Merges thirdparty modules into this repo.
This commit is contained in:
parent
acb898ad9b
commit
7802ad1211
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -1,9 +0,0 @@
|
||||
[submodule "thirdparty/dast"]
|
||||
path = thirdparty/dast
|
||||
url = https://gogs.enigmatical.work/kajironagi/dast.git
|
||||
[submodule "thirdparty/gl4d"]
|
||||
path = thirdparty/gl4d
|
||||
url = https://gogs.enigmatical.work/kajironagi/gl4d.git
|
||||
[submodule "thirdparty/ft4d"]
|
||||
path = thirdparty/ft4d
|
||||
url = https://gogs.enigmatical.work/KajiroNagi/ft4d.git
|
1
thirdparty/dast
vendored
1
thirdparty/dast
vendored
@ -1 +0,0 @@
|
||||
Subproject commit d99fcb9bf502683ab4e5f6fb19a64a3bda98c83e
|
4
thirdparty/dast/.gitignore
vendored
Normal file
4
thirdparty/dast/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/.bin
|
||||
/.dub
|
||||
|
||||
*.swp
|
6
thirdparty/dast/dub.json
vendored
Normal file
6
thirdparty/dast/dub.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "dast",
|
||||
|
||||
"targetType": "library",
|
||||
"targetPath": ".bin"
|
||||
}
|
16
thirdparty/dast/src/dast/parse/exception.d
vendored
Normal file
16
thirdparty/dast/src/dast/parse/exception.d
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/// License: MIT
|
||||
module dast.parse.exception;
|
||||
|
||||
import dast.tokenize;
|
||||
|
||||
///
|
||||
class ParseException(Token) : Exception if (IsToken!Token) {
|
||||
public:
|
||||
///
|
||||
this(string msg, Token token, string file = __FILE__, size_t line = __LINE__) {
|
||||
super(msg, file, line);
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
Token token;
|
||||
}
|
219
thirdparty/dast/src/dast/parse/itemset.d
vendored
Normal file
219
thirdparty/dast/src/dast/parse/itemset.d
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
/// License: MIT
|
||||
module dast.parse.itemset;
|
||||
|
||||
import std.algorithm,
|
||||
std.array,
|
||||
std.conv,
|
||||
std.range,
|
||||
std.range.primitives,
|
||||
std.traits,
|
||||
std.typecons;
|
||||
|
||||
import dast.parse.rule,
|
||||
dast.parse.ruleset,
|
||||
dast.util.range;
|
||||
|
||||
///
|
||||
unittest {
|
||||
import std;
|
||||
|
||||
enum TokenType {
|
||||
Ident,
|
||||
End,
|
||||
}
|
||||
alias Token = dast.tokenize.Token!(TokenType, string);
|
||||
|
||||
struct RuleSet {
|
||||
public:
|
||||
@ParseRule:
|
||||
int ParseWhole(string, @(TokenType.End) Token) {
|
||||
assert(false);
|
||||
}
|
||||
string ParseString(@(TokenType.Ident) Token) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
enum rules = CreateRulesFromRuleSet!(RuleSet, TokenType)();
|
||||
enum itemset = rules.CreateItemSetFromRules(typeid(int));
|
||||
}
|
||||
|
||||
///
|
||||
struct RuleWithCursor(T) if (IsRule!T) {
|
||||
public:
|
||||
alias entity this;
|
||||
|
||||
///
|
||||
bool opEquals(in RuleWithCursor other) const {
|
||||
return
|
||||
entity == other.entity &&
|
||||
cursor == other.cursor &&
|
||||
follows.equal(other.follows);
|
||||
}
|
||||
|
||||
///
|
||||
@property RuleWithCursor advanced() const in (canAdvance) {
|
||||
return RuleWithCursor(entity, cursor+1, follows);
|
||||
}
|
||||
|
||||
///
|
||||
@property Term!(T.TokenType) prev() const in (cursor > 0) {
|
||||
return cast(Term!(T.TokenType)) entity.rhs[cursor-1];
|
||||
}
|
||||
///
|
||||
@property Term!(T.TokenType) next() const in (canAdvance) {
|
||||
return cast(Term!(T.TokenType)) entity.rhs[cursor];
|
||||
}
|
||||
|
||||
///
|
||||
@property bool canAdvance() const {
|
||||
return cursor < entity.rhs.length;
|
||||
}
|
||||
|
||||
///
|
||||
const T entity;
|
||||
|
||||
///
|
||||
const size_t cursor;
|
||||
|
||||
///
|
||||
const T.TokenType[] follows;
|
||||
}
|
||||
|
||||
///
|
||||
struct ItemSet(T) if (IsRule!T) {
|
||||
public:
|
||||
///
|
||||
TypeInfo whole;
|
||||
|
||||
///
|
||||
const Term!(T.TokenType)[] terms;
|
||||
///
|
||||
const size_t[size_t][] automaton;
|
||||
///
|
||||
const RuleWithCursor!T[][] states;
|
||||
}
|
||||
|
||||
///
|
||||
ItemSet!(ElementType!R) CreateItemSetFromRules(R)(R rules, TypeInfo whole)
|
||||
if (isInputRange!R && IsRule!(ElementType!R))
|
||||
in {
|
||||
assert(rules.count!(x => x.lhs is whole) == 1);
|
||||
}
|
||||
do {
|
||||
alias T = ElementType!R;
|
||||
|
||||
Term!(T.TokenType)[] terms;
|
||||
size_t FindOrRegisterTermIndices(typeof(terms[0]) term) {
|
||||
auto index = terms.countUntil!(x => x == term);
|
||||
if (index < 0) {
|
||||
index = terms.length.to!(typeof(index));
|
||||
terms ~= term;
|
||||
}
|
||||
return index.to!size_t;
|
||||
}
|
||||
|
||||
RuleWithCursor!T[] Resolve(
|
||||
RuleWithCursor!T[] items, TypeInfo[] prev_resolved_types = [null]) {
|
||||
auto resolved_types = prev_resolved_types.appender;
|
||||
|
||||
T[] new_items;
|
||||
foreach (item; items) {
|
||||
const type =
|
||||
!item.canAdvance || item.next.isTerminal? null: item.next.nonTerminalType;
|
||||
if (resolved_types[].canFind!"a is b"(type)) continue;
|
||||
|
||||
rules.
|
||||
filter!(x => x.lhs is type).
|
||||
each !(x => new_items ~= x);
|
||||
resolved_types ~= type;
|
||||
}
|
||||
|
||||
auto result = items ~ new_items.
|
||||
map!(x => RuleWithCursor!T(x)).
|
||||
array;
|
||||
return new_items.length > 0?
|
||||
Resolve(result, resolved_types[]): result;
|
||||
}
|
||||
RuleWithCursor!T[][size_t] Advance(in RuleWithCursor!T[] items) {
|
||||
RuleWithCursor!T[][size_t] new_items;
|
||||
|
||||
foreach (item; items.filter!"a.canAdvance".map!"a.advanced") {
|
||||
auto term = item.prev;
|
||||
const index = FindOrRegisterTermIndices(term);
|
||||
if (index !in new_items) {
|
||||
new_items[index] = [];
|
||||
}
|
||||
new_items[index] ~= item;
|
||||
}
|
||||
foreach (ref state; new_items) {
|
||||
state = Resolve(state).AttachFollowSet();
|
||||
}
|
||||
return new_items;
|
||||
}
|
||||
|
||||
size_t[size_t][] automaton;
|
||||
RuleWithCursor!T[][] states;
|
||||
|
||||
const first_rule = rules.find!"a.lhs is b"(whole)[0];
|
||||
|
||||
size_t current_state;
|
||||
states = [Resolve([RuleWithCursor!T(first_rule)]).AttachFollowSet()];
|
||||
|
||||
while (true) {
|
||||
automaton.length = current_state+1;
|
||||
|
||||
const advanced_states = Advance(states[current_state]);
|
||||
foreach (termi, state; advanced_states) {
|
||||
auto index = states.countUntil!(x => x.equal(state));
|
||||
if (index < 0) {
|
||||
index = states.length.to!(typeof(index));
|
||||
states ~= state.dup;
|
||||
}
|
||||
automaton[current_state][termi] = index.to!size_t;
|
||||
}
|
||||
if (++current_state >= states.length) break;
|
||||
}
|
||||
return ItemSet!T(whole, terms, automaton, states);
|
||||
}
|
||||
|
||||
private RuleWithCursor!T[] AttachFollowSet(T)(RuleWithCursor!T[] rules) {
|
||||
alias TokenType = T.TokenType;
|
||||
|
||||
TokenType[] CreateFollowSet(TypeInfo type, TypeInfo[] skip_types = []) {
|
||||
if (skip_types.canFind!"a is b"(type)) return [];
|
||||
|
||||
auto result = appender!(TokenType[]);
|
||||
skip_types ~= type;
|
||||
|
||||
foreach (const ref rule; rules) {
|
||||
if (rule.lhs is type) {
|
||||
result ~= rule.follows;
|
||||
}
|
||||
if (!rule.canAdvance || rule.next != type) continue;
|
||||
|
||||
if (rule.cursor+1 < rule.rhs.length) {
|
||||
const follow_term = rule.rhs[rule.cursor+1];
|
||||
if (follow_term.isTerminal) {
|
||||
result ~= follow_term.terminalType;
|
||||
} else {
|
||||
result ~= rules.CreateFirstSet!TokenType(follow_term.nonTerminalType);
|
||||
}
|
||||
} else {
|
||||
result ~= CreateFollowSet(rule.lhs, skip_types);
|
||||
}
|
||||
}
|
||||
return result[];
|
||||
}
|
||||
|
||||
TokenType[][TypeInfo] caches;
|
||||
|
||||
RuleWithCursor!T[] result;
|
||||
foreach (const ref rule; rules) {
|
||||
if (rule.lhs !in caches) {
|
||||
caches[rule.lhs] = CreateFollowSet(rule.lhs).DropDuplicated;
|
||||
}
|
||||
result ~= RuleWithCursor!T(rule.entity, rule.cursor, caches[rule.lhs]);
|
||||
}
|
||||
return result;
|
||||
}
|
201
thirdparty/dast/src/dast/parse/package.d
vendored
Normal file
201
thirdparty/dast/src/dast/parse/package.d
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
/// License: MIT
|
||||
module dast.parse;
|
||||
|
||||
import std.algorithm,
|
||||
std.conv,
|
||||
std.range.primitives,
|
||||
std.traits,
|
||||
std.variant;
|
||||
|
||||
import dast.tokenize;
|
||||
|
||||
import dast.parse.itemset,
|
||||
dast.parse.rule,
|
||||
dast.parse.ruleset,
|
||||
dast.parse.table;
|
||||
|
||||
public {
|
||||
import dast.parse.exception,
|
||||
dast.parse.ruleset;
|
||||
}
|
||||
|
||||
///
|
||||
unittest {
|
||||
import std;
|
||||
|
||||
enum TokenType {
|
||||
Unknown,
|
||||
|
||||
@TextCompleteMatcher!"," Comma,
|
||||
|
||||
@TextAllMatcher!isDigit Number,
|
||||
@TextAllMatcher!isWhite Whitespace,
|
||||
|
||||
End,
|
||||
}
|
||||
alias Token = dast.tokenize.Token!(TokenType, string);
|
||||
|
||||
struct Whole {
|
||||
int[] numbers;
|
||||
}
|
||||
class RuleSet {
|
||||
public:
|
||||
@ParseRule:
|
||||
Whole ParseWhole(int[] nums, @(TokenType.End) Token) {
|
||||
return Whole(nums);
|
||||
}
|
||||
int[] ParseFirstNumber(@(TokenType.Number) Token num) {
|
||||
return [num.text.to!int];
|
||||
}
|
||||
int[] ParseNextNumber(int[] nums, @(TokenType.Comma) Token, @(TokenType.Number) Token num) {
|
||||
return nums ~ num.text.to!int;
|
||||
}
|
||||
}
|
||||
|
||||
auto ruleset = new RuleSet;
|
||||
|
||||
int[] ParseNumbers(string src) {
|
||||
return src.
|
||||
Tokenize!TokenType.
|
||||
filter!(x => x.type != TokenType.Whitespace).
|
||||
chain([Token("", TokenType.End)]).
|
||||
Parse!Whole(ruleset).numbers;
|
||||
}
|
||||
assert(ParseNumbers("0, 1, 2, 3").equal([0, 1, 2, 3]));
|
||||
|
||||
assertThrown!(ParseException!Token)(ParseNumbers("0 1 2 3"));
|
||||
}
|
||||
|
||||
///
|
||||
Whole Parse(Whole, Tokenizer, RuleSet)(Tokenizer tokenizer, RuleSet ruleset)
|
||||
if (isInputRange!Tokenizer && IsToken!(ElementType!Tokenizer)) {
|
||||
alias Token = ElementType!Tokenizer;
|
||||
alias Exception = ParseException!Token;
|
||||
|
||||
enum rules = CreateRulesFromRuleSet!(RuleSet, Token.TokenType);
|
||||
static assert(rules.length > 0);
|
||||
|
||||
enum itemset = rules.CreateItemSetFromRules(typeid(Whole));
|
||||
enum table = itemset.CreateTableFromItemSet();
|
||||
enum statelen = table.shift.length;
|
||||
|
||||
enum stackitem_size = itemset.terms.
|
||||
filter!"!a.isTerminal".
|
||||
map !"a.nonTerminalType.tsize".
|
||||
maxElement.
|
||||
max(Token.sizeof);
|
||||
|
||||
alias StackItem = VariantN!(stackitem_size);
|
||||
|
||||
StackItem[] stack;
|
||||
size_t[] status = [0];
|
||||
|
||||
size_t Reduce(Token, string name)() {
|
||||
alias Func = mixin("RuleSet."~name);
|
||||
alias Params = Parameters!Func;
|
||||
|
||||
Params params;
|
||||
static foreach (parami; 0..Params.length) {
|
||||
static if (is(Params[$-parami-1] == Token)) {
|
||||
params[$-parami-1] = stack[$-parami-1].get!Token;
|
||||
} else {
|
||||
params[$-parami-1] = stack[$-parami-1].get!(Params[$-parami-1]);
|
||||
}
|
||||
}
|
||||
stack = stack[0..$-Params.length];
|
||||
stack ~= StackItem(mixin("ruleset."~name~"(params)"));
|
||||
return Params.length;
|
||||
}
|
||||
|
||||
void GoStateAfterReduce() {
|
||||
auto stack_top_type = stack[$-1].type;
|
||||
const current_status = status[$-1];
|
||||
|
||||
TypeInfo temp;
|
||||
static foreach (statei; 0..statelen) {
|
||||
if (statei == current_status) {
|
||||
static foreach (type, number; table.go[statei]) {
|
||||
if (type is stack_top_type) {
|
||||
status ~= table.go[statei][stack_top_type];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(false); // broken go table
|
||||
}
|
||||
|
||||
Token prev_token;
|
||||
while (status[$-1] != table.accept_state) {
|
||||
if (tokenizer.empty) {
|
||||
throw new Exception("all tokens are consumed without acception", prev_token);
|
||||
}
|
||||
const token = tokenizer.front;
|
||||
scope(exit) prev_token = token;
|
||||
|
||||
MAIN_SWITCH: switch (status[$-1]) {
|
||||
static foreach (statei; 0..statelen) {
|
||||
case statei:
|
||||
static foreach (token_type, reduce; table.reduce[statei]) {
|
||||
if (token_type == token.type) {
|
||||
const pop = Reduce!(Token, reduce);
|
||||
status = status[0..$-pop];
|
||||
GoStateAfterReduce();
|
||||
break MAIN_SWITCH;
|
||||
}
|
||||
}
|
||||
static foreach (token_type, shift; table.shift[statei]) {
|
||||
if (token_type == token.type) {
|
||||
stack ~= StackItem(cast(Unqual!Token) token);
|
||||
status ~= shift;
|
||||
tokenizer.popFront();
|
||||
break MAIN_SWITCH;
|
||||
}
|
||||
}
|
||||
throw new Exception("unexpected token", token);
|
||||
}
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
Reduce!(Token, itemset.states[0][0].name);
|
||||
return stack[0].get!Whole;
|
||||
}
|
||||
|
||||
debug void PrintItemSet(TokenType, RuleSet, Whole)()
|
||||
if (is(TokenType == enum)) {
|
||||
import std;
|
||||
|
||||
enum itemset =
|
||||
CreateRulesFromRuleSet!(RuleSet, TokenType).
|
||||
CreateItemSetFromRules(typeid(Whole));
|
||||
|
||||
void PrintTerm(T2)(T2 term) {
|
||||
if (term.isTerminal) {
|
||||
term.terminalType.write;
|
||||
} else {
|
||||
term.nonTerminalType.write;
|
||||
}
|
||||
}
|
||||
|
||||
"[states]".writeln;
|
||||
foreach (statei, state; itemset.states) {
|
||||
" (%d) ".writef(statei);
|
||||
foreach (termi, go; itemset.automaton[statei]) {
|
||||
PrintTerm(itemset.terms[termi]);
|
||||
":%d, ".writef(go);
|
||||
}
|
||||
writeln;
|
||||
|
||||
foreach (rule; state) {
|
||||
" %s => ".writef(rule.lhs);
|
||||
foreach (termi, term; rule.rhs) {
|
||||
if (termi == rule.cursor) "/ ".write;
|
||||
PrintTerm(term);
|
||||
" ".write;
|
||||
}
|
||||
if (rule.rhs.length == rule.cursor) "/ ".write;
|
||||
rule.follows.writeln;
|
||||
}
|
||||
}
|
||||
}
|
172
thirdparty/dast/src/dast/parse/rule.d
vendored
Normal file
172
thirdparty/dast/src/dast/parse/rule.d
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/// License: MIT
|
||||
module dast.parse.rule;
|
||||
|
||||
import std.algorithm,
|
||||
std.array,
|
||||
std.traits;
|
||||
|
||||
import dast.tokenize;
|
||||
|
||||
///
|
||||
unittest {
|
||||
enum TokenType {
|
||||
Number,
|
||||
}
|
||||
alias Token = dast.tokenize.Token!(TokenType, string);
|
||||
|
||||
struct RuleSet {
|
||||
public:
|
||||
int ParseWhole(@(TokenType.Number) Token, string v) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
enum rule_ctfe = Rule!TokenType.CreateFromNonOverloadedMethod!(RuleSet, "ParseWhole");
|
||||
|
||||
static assert(rule_ctfe.lhs is typeid(int));
|
||||
|
||||
static assert(rule_ctfe.rhs.length == 2);
|
||||
static assert(rule_ctfe.rhs[0] == TokenType.Number);
|
||||
static assert(rule_ctfe.rhs[1] == typeid(string));
|
||||
}
|
||||
|
||||
///
|
||||
template IsRule(T) {
|
||||
static if (__traits(compiles, T.TokenType)) {
|
||||
enum IsRule = is(T : Rule!(T.TokenType));
|
||||
} else {
|
||||
enum IsRule = false;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
struct NamedRule(TokenType) if (is(TokenType == enum)) {
|
||||
public:
|
||||
alias entity this;
|
||||
|
||||
///
|
||||
const string name;
|
||||
///
|
||||
const Rule!TokenType entity;
|
||||
}
|
||||
|
||||
///
|
||||
struct Rule(TokenType_) if (is(TokenType_ == enum)) {
|
||||
public:
|
||||
///
|
||||
alias TokenType = TokenType_;
|
||||
|
||||
///
|
||||
static Rule CreateFromNonOverloadedMethod(T, string name)()
|
||||
if (__traits(hasMember, T, name)) {
|
||||
alias method = mixin("T." ~ name);
|
||||
alias Params = Parameters!method;
|
||||
|
||||
auto rhs = appender!(Term!TokenType[])();
|
||||
static foreach (parami; 0..Params.length) {
|
||||
static if (IsToken!(Params[parami])) {{
|
||||
static foreach (attr; __traits(getAttributes, Params[parami..parami+1])) {
|
||||
static if (is(typeof(attr) == TokenType)) {
|
||||
static assert(!__traits(compiles, found_attr));
|
||||
enum found_attr = true;
|
||||
rhs ~= Term!TokenType(attr);
|
||||
}
|
||||
}
|
||||
static assert(__traits(compiles, found_attr));
|
||||
}} else {
|
||||
rhs ~= Term!TokenType(TypeInfoWithSize.CreateFromType!(Params[parami]));
|
||||
}
|
||||
}
|
||||
return Rule(typeid(ReturnType!method), rhs.array);
|
||||
}
|
||||
|
||||
///
|
||||
bool opEquals(Rule other) const {
|
||||
return lhs is other.lhs && rhs_.equal(other.rhs_);
|
||||
}
|
||||
|
||||
///
|
||||
@property TypeInfo lhs() const {
|
||||
return cast(TypeInfo) lhs_;
|
||||
}
|
||||
///
|
||||
@property const(Term!TokenType[]) rhs() const {
|
||||
return rhs_;
|
||||
}
|
||||
|
||||
private:
|
||||
const TypeInfo lhs_;
|
||||
invariant(lhs_ !is null);
|
||||
|
||||
const Term!TokenType[] rhs_;
|
||||
invariant(rhs_.length > 0);
|
||||
}
|
||||
|
||||
///
|
||||
struct Term(TokenType) if (is(TokenType == enum)) {
|
||||
public:
|
||||
@disable this();
|
||||
|
||||
///
|
||||
this(TypeInfoWithSize type) {
|
||||
data_.type_info = type;
|
||||
terminal_ = false;
|
||||
}
|
||||
///
|
||||
this(TokenType type) {
|
||||
data_.token_type = type;
|
||||
terminal_ = true;
|
||||
}
|
||||
|
||||
///
|
||||
bool opEquals(in Term rhs) const {
|
||||
return isTerminal?
|
||||
rhs.isTerminal && terminalType == rhs.terminalType:
|
||||
!rhs.isTerminal && nonTerminalType is rhs.nonTerminalType;
|
||||
}
|
||||
///
|
||||
bool opEquals(in TypeInfo type) const {
|
||||
return !isTerminal && nonTerminalType is type;
|
||||
}
|
||||
///
|
||||
bool opEquals(TokenType type) const {
|
||||
return isTerminal && terminalType == type;
|
||||
}
|
||||
|
||||
///
|
||||
@property bool isTerminal() const {
|
||||
return terminal_;
|
||||
}
|
||||
|
||||
///
|
||||
@property TokenType terminalType() const in (isTerminal) {
|
||||
return data_.token_type;
|
||||
}
|
||||
///
|
||||
@property TypeInfoWithSize nonTerminalType() const in (!isTerminal) {
|
||||
return cast(TypeInfoWithSize) data_.type_info;
|
||||
}
|
||||
|
||||
private:
|
||||
union Data {
|
||||
TypeInfoWithSize type_info;
|
||||
TokenType token_type;
|
||||
}
|
||||
Data data_;
|
||||
bool terminal_;
|
||||
}
|
||||
|
||||
///
|
||||
struct TypeInfoWithSize {
|
||||
public:
|
||||
alias entity this;
|
||||
|
||||
///
|
||||
static TypeInfoWithSize CreateFromType(T)() {
|
||||
return TypeInfoWithSize(T.sizeof, typeid(T));
|
||||
}
|
||||
|
||||
///
|
||||
const size_t tsize;
|
||||
///
|
||||
TypeInfo entity;
|
||||
}
|
96
thirdparty/dast/src/dast/parse/ruleset.d
vendored
Normal file
96
thirdparty/dast/src/dast/parse/ruleset.d
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
/// License: MIT
|
||||
module dast.parse.ruleset;
|
||||
|
||||
import std.algorithm,
|
||||
std.array,
|
||||
std.meta,
|
||||
std.range.primitives,
|
||||
std.traits;
|
||||
|
||||
import dast.tokenize;
|
||||
|
||||
import dast.parse.rule;
|
||||
|
||||
///
|
||||
unittest {
|
||||
import std;
|
||||
import dast.util.range;
|
||||
|
||||
enum TokenType {
|
||||
Number,
|
||||
Ident,
|
||||
End,
|
||||
}
|
||||
alias Token = dast.tokenize.Token!(TokenType, string);
|
||||
|
||||
struct RuleSet {
|
||||
public:
|
||||
@ParseRule:
|
||||
int ParseWhole(string, @(TokenType.End) Token) {
|
||||
assert(false);
|
||||
}
|
||||
string ParseStr(@(TokenType.Ident) Token) {
|
||||
assert(false);
|
||||
}
|
||||
string ParseStrFromFloat(float) {
|
||||
assert(false);
|
||||
}
|
||||
float ParseFloat(@(TokenType.Number) Token) {
|
||||
assert(false);
|
||||
}
|
||||
float ParseFloatFromStr(@(TokenType.Ident) Token, string, @(TokenType.Ident) Token) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
enum rules = CreateRulesFromRuleSet!(RuleSet, TokenType)();
|
||||
static assert(rules.length == 5);
|
||||
|
||||
enum firsts = rules.CreateFirstSet!TokenType(typeid(int)).DropDuplicated;
|
||||
static assert(firsts.equal([TokenType.Ident, TokenType.Number]));
|
||||
}
|
||||
|
||||
/// A struct which is attributed to rule methods.
|
||||
struct ParseRule {}
|
||||
|
||||
/// Creates rules from RuleSet's methods attributed by ParseRule.
|
||||
NamedRule!TokenType[] CreateRulesFromRuleSet(RuleSet, TokenType)() {
|
||||
alias members = __traits(allMembers, RuleSet);
|
||||
|
||||
NamedRule!TokenType[] result; // Appender causes CTFE errors :(
|
||||
|
||||
static foreach (name; members) {{
|
||||
enum method = "RuleSet." ~ name;
|
||||
static if (__traits(compiles, mixin(method))) {
|
||||
alias attrs = __traits(getAttributes, mixin(method));
|
||||
static if (staticIndexOf!(ParseRule, attrs) >= 0) {
|
||||
static assert(__traits(getOverloads, RuleSet, name).length == 1);
|
||||
auto rule = Rule!TokenType.
|
||||
CreateFromNonOverloadedMethod!(RuleSet, name);
|
||||
result ~= NamedRule!TokenType(name, rule);
|
||||
}
|
||||
}
|
||||
}}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Creates a first set of the target.
|
||||
///
|
||||
/// Items in the result can be duplicated.
|
||||
TokenType[] CreateFirstSet(TokenType, R)(R rules, TypeInfo target, TypeInfo[] skip_types = [])
|
||||
if (isInputRange!R && is(Unqual!(ElementType!R) : Rule!TokenType)) {
|
||||
auto result = appender!(TokenType[]);
|
||||
skip_types ~= target;
|
||||
|
||||
foreach (const ref rule; rules.filter!(x => x.lhs is target)) {
|
||||
const first = rule.rhs[0];
|
||||
if (first.isTerminal) {
|
||||
result ~= first.terminalType;
|
||||
} else if (!skip_types.canFind!"a is b"(first.nonTerminalType)) {
|
||||
result ~= rules.
|
||||
CreateFirstSet!(TokenType)(first.nonTerminalType, skip_types);
|
||||
skip_types ~= first.nonTerminalType;
|
||||
}
|
||||
}
|
||||
return result.array;
|
||||
}
|
80
thirdparty/dast/src/dast/parse/table.d
vendored
Normal file
80
thirdparty/dast/src/dast/parse/table.d
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/// License: MIT
|
||||
module dast.parse.table;
|
||||
|
||||
import std.algorithm;
|
||||
|
||||
import dast.parse.itemset,
|
||||
dast.parse.rule,
|
||||
dast.parse.ruleset;
|
||||
|
||||
///
|
||||
unittest {
|
||||
import std;
|
||||
|
||||
enum TokenType {
|
||||
Ident,
|
||||
End,
|
||||
}
|
||||
alias Token = dast.tokenize.Token!(TokenType, string);
|
||||
|
||||
struct RuleSet {
|
||||
public:
|
||||
@ParseRule:
|
||||
int ParseWhole(string, @(TokenType.End) Token) {
|
||||
assert(false);
|
||||
}
|
||||
string ParseString(@(TokenType.Ident) Token) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
enum rules = CreateRulesFromRuleSet!(RuleSet, TokenType)();
|
||||
enum itemset = rules.CreateItemSetFromRules(typeid(int));
|
||||
enum table = itemset.CreateTableFromItemSet();
|
||||
}
|
||||
|
||||
///
|
||||
struct Table(TokenType) if (is(TokenType == enum)) {
|
||||
public:
|
||||
///
|
||||
const size_t[TokenType][] shift;
|
||||
///
|
||||
const string[TokenType][] reduce;
|
||||
///
|
||||
const size_t[TypeInfo][] go;
|
||||
|
||||
///
|
||||
const size_t accept_state;
|
||||
}
|
||||
|
||||
///
|
||||
Table!(T.TokenType) CreateTableFromItemSet(T)(in ItemSet!T itemset) {
|
||||
size_t[T.TokenType][] shift;
|
||||
string[T.TokenType][] reduce;
|
||||
size_t[TypeInfo][] go;
|
||||
|
||||
size_t accept_state;
|
||||
|
||||
shift.length = itemset.automaton.length;
|
||||
reduce.length = itemset.automaton.length;
|
||||
go.length = itemset.automaton.length;
|
||||
|
||||
foreach (state, numbers; itemset.automaton) {
|
||||
foreach (k, v; numbers) {
|
||||
const term = itemset.terms[k];
|
||||
if (term.isTerminal) {
|
||||
shift[state][term.terminalType] = v;
|
||||
} else {
|
||||
go[state][term.nonTerminalType] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (state, rules; itemset.states) {
|
||||
foreach (rule; rules.filter!"!a.canAdvance") {
|
||||
if (rule.lhs is itemset.whole) accept_state = state;
|
||||
rule.follows.each!(x => reduce[state][x] = rule.name);
|
||||
}
|
||||
}
|
||||
return Table!(T.TokenType)(shift, reduce, go, accept_state);
|
||||
}
|
44
thirdparty/dast/src/dast/tokenize/data.d
vendored
Normal file
44
thirdparty/dast/src/dast/tokenize/data.d
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
/// License: MIT
|
||||
module dast.tokenize.data;
|
||||
|
||||
import std.traits;
|
||||
|
||||
///
|
||||
template IsToken(T) {
|
||||
private enum hasRequiredMembers =
|
||||
__traits(hasMember, T, "text") &&
|
||||
__traits(hasMember, T, "type") &&
|
||||
__traits(hasMember, T, "pos");
|
||||
|
||||
static if (hasRequiredMembers) {
|
||||
enum IsToken =
|
||||
isSomeString!(typeof(T.text)) &&
|
||||
is(typeof(T.type) == enum) &&
|
||||
is(typeof(T.pos) == TokenPos);
|
||||
} else {
|
||||
enum IsToken = false;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
enum IsToken(T, S) = IsToken!T && is(typeof(T.text) == S);
|
||||
|
||||
///
|
||||
struct Token(TokenType_, S)
|
||||
if (is(TokenType_ == enum) && isSomeString!S) {
|
||||
alias TokenType = TokenType_;
|
||||
|
||||
S text;
|
||||
TokenType type;
|
||||
|
||||
TokenPos pos;
|
||||
}
|
||||
|
||||
///
|
||||
struct TokenPos {
|
||||
size_t stline;
|
||||
size_t edline;
|
||||
|
||||
size_t stchar;
|
||||
size_t edchar;
|
||||
}
|
31
thirdparty/dast/src/dast/tokenize/exception.d
vendored
Normal file
31
thirdparty/dast/src/dast/tokenize/exception.d
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
/// License: MIT
|
||||
module dast.tokenize.exception;
|
||||
|
||||
import std.format;
|
||||
|
||||
///
|
||||
class TokenizeException : Exception {
|
||||
public:
|
||||
///
|
||||
this(string msg,
|
||||
size_t srcline,
|
||||
size_t srchar,
|
||||
string src = "",
|
||||
string file = __FILE__,
|
||||
size_t line = __LINE__) {
|
||||
if (src == "") {
|
||||
msg ~= " at (%d, %d)".format(srcline, srcchar);
|
||||
} else {
|
||||
msg ~= " at token '%s' (%d, %d)".format(src, srcline, srcchar);
|
||||
}
|
||||
super(msg, file, line);
|
||||
|
||||
this.srcline = srcline;
|
||||
this.srcchar = srcchar;
|
||||
}
|
||||
|
||||
///
|
||||
const size_t srcline;
|
||||
///
|
||||
const size_t srcchar;
|
||||
}
|
142
thirdparty/dast/src/dast/tokenize/match.d
vendored
Normal file
142
thirdparty/dast/src/dast/tokenize/match.d
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
/// License: MIT
|
||||
module dast.tokenize.match;
|
||||
|
||||
import std.algorithm,
|
||||
std.ascii,
|
||||
std.conv,
|
||||
std.traits;
|
||||
|
||||
///
|
||||
unittest {
|
||||
import std;
|
||||
|
||||
enum TokenType {
|
||||
@TextAllMatcher!isDigit Number,
|
||||
@TextCompleteMatcher!"12" OneTwo,
|
||||
|
||||
@TextFuncMatcher!((string text) {
|
||||
return text.length >= 3? 3LU: 0LU;
|
||||
}) ThreeLetters,
|
||||
|
||||
@TextAllMatcher!isUpper
|
||||
@TextAllMatcher!isLower Ident,
|
||||
}
|
||||
|
||||
with (TokenType) {
|
||||
assert(MatchTextAsTokenType!Number("0123") == 4);
|
||||
assert(MatchTextAsTokenType!Number("01a23") == 2);
|
||||
assert(MatchTextAsTokenType!Number("a0123") == 0);
|
||||
|
||||
assert(MatchTextAsTokenType!OneTwo("12") == 2);
|
||||
assert(MatchTextAsTokenType!OneTwo("12a") == 2);
|
||||
assert(MatchTextAsTokenType!OneTwo("1") == 0);
|
||||
|
||||
assert(MatchTextAsTokenType!ThreeLetters("abc") == 3);
|
||||
assert(MatchTextAsTokenType!ThreeLetters("abcd") == 3);
|
||||
assert(MatchTextAsTokenType!ThreeLetters("ab") == 0);
|
||||
|
||||
assert(MatchTextAsTokenType!Ident("abC") == 2);
|
||||
assert(MatchTextAsTokenType!Ident("AB0") == 2);
|
||||
assert(MatchTextAsTokenType!Ident("0Ab") == 0);
|
||||
|
||||
{
|
||||
const result = FindMatchedTokenTypes!TokenType("012");
|
||||
assert(result.types.equal([Number, ThreeLetters]));
|
||||
assert(result.length == 3);
|
||||
}
|
||||
{
|
||||
const result = FindMatchedTokenTypes!TokenType("12");
|
||||
assert(result.types.equal([Number, OneTwo]));
|
||||
assert(result.length == 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the matcher is a text matcher for S.
|
||||
enum IsTextMatcher(alias matcher, S) =
|
||||
__traits(compiles, (S str) => matcher.Match(str)) &&
|
||||
is(ReturnType!((S str) => matcher.Match(str)) == size_t);
|
||||
/// ditto
|
||||
enum IsTextMatcher(alias matcher) =
|
||||
IsTextMatcher!(matcher, string) ||
|
||||
IsTextMatcher!(matcher, wstring) ||
|
||||
IsTextMatcher!(matcher, dstring);
|
||||
|
||||
///
|
||||
struct TextAllMatcher(alias func) {
|
||||
public:
|
||||
///
|
||||
static size_t Match(S)(S text) if (isSomeString!S) {
|
||||
const index = text.countUntil!(x => !func(x));
|
||||
return index >= 0? index.to!size_t: text.length;
|
||||
}
|
||||
static assert(IsTextMatcher!TextAllMatcher);
|
||||
}
|
||||
|
||||
///
|
||||
struct TextCompleteMatcher(alias str) if (isSomeString!(typeof(str))) {
|
||||
public:
|
||||
///
|
||||
static size_t Match(typeof(str) text) {
|
||||
if (text.length < str.length || text[0..str.length] != str) {
|
||||
return 0;
|
||||
}
|
||||
return str.length;
|
||||
}
|
||||
static assert(IsTextMatcher!TextCompleteMatcher);
|
||||
}
|
||||
|
||||
///
|
||||
struct TextFuncMatcher(alias func)
|
||||
if ((is(typeof((string x) => func(x))) ||
|
||||
is(typeof((wstring x) => func(x))) ||
|
||||
is(typeof((dstring x) => func(x)))) &&
|
||||
is(ReturnType!func == size_t)) {
|
||||
public:
|
||||
///
|
||||
static size_t Match(S)(S text)
|
||||
if (isSomeString!S && __traits(compiles, func(text))) {
|
||||
return func(text);
|
||||
}
|
||||
static assert(IsTextMatcher!(TextFuncMatcher, string));
|
||||
}
|
||||
|
||||
/// Finds matched token types from TokenType enum.
|
||||
auto FindMatchedTokenTypes(TokenType, S)(S src)
|
||||
if (is(TokenType == enum) && isSomeString!S) {
|
||||
struct Result {
|
||||
TokenType[] types;
|
||||
size_t length;
|
||||
}
|
||||
Result result;
|
||||
|
||||
size_t length = void;
|
||||
static foreach (type; EnumMembers!TokenType) {
|
||||
length = MatchTextAsTokenType!type(src);
|
||||
if (length == 0) {
|
||||
|
||||
} else if (length > result.length) {
|
||||
result.types = [type];
|
||||
result.length = length;
|
||||
|
||||
} else if (length == result.length) {
|
||||
result.types ~= type;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Checks if the src can be a token with the type.
|
||||
size_t MatchTextAsTokenType(alias type, S)(S src)
|
||||
if (is(typeof(type) == enum) && isSomeString!S) {
|
||||
alias TokenType = typeof(type);
|
||||
enum typestr = "TokenType." ~ type.to!string;
|
||||
|
||||
size_t result;
|
||||
static foreach (attr; __traits(getAttributes, mixin(typestr))) {
|
||||
static if (IsTextMatcher!(attr, S)) {
|
||||
result = result.max(attr.Match(src));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
122
thirdparty/dast/src/dast/tokenize/package.d
vendored
Normal file
122
thirdparty/dast/src/dast/tokenize/package.d
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
/// License: MIT
|
||||
module dast.tokenize;
|
||||
|
||||
import std.format,
|
||||
std.range,
|
||||
std.traits,
|
||||
std.typecons;
|
||||
|
||||
public {
|
||||
import dast.tokenize.data,
|
||||
dast.tokenize.exception,
|
||||
dast.tokenize.match;
|
||||
}
|
||||
|
||||
///
|
||||
unittest {
|
||||
import std;
|
||||
|
||||
enum TokenType {
|
||||
@TextAllMatcher!isDigit Number,
|
||||
@TextAllMatcher!isAlpha Ident,
|
||||
@TextAllMatcher!isWhite Whitespace,
|
||||
|
||||
@TextCompleteMatcher!"0" Zero,
|
||||
@TextCompleteMatcher!"0a" ZeroAndA,
|
||||
}
|
||||
|
||||
with (TokenType) {
|
||||
auto tokens = "1 abc 2".Tokenize!(TokenType, string)();
|
||||
assert(tokens.
|
||||
map!"a.type".
|
||||
equal([Number, Whitespace, Ident, Whitespace, Number]));
|
||||
assert(tokens.
|
||||
filter!(x => x.type != Whitespace).
|
||||
map!"a.text".
|
||||
equal(["1", "abc", "2"]));
|
||||
}
|
||||
with (TokenType) {
|
||||
auto tokens = "0a 1 2".Tokenize!(TokenType, string)();
|
||||
assert(tokens.
|
||||
map!"a.type".
|
||||
equal([ZeroAndA, Whitespace, Number, Whitespace, Number]));
|
||||
assert(tokens.
|
||||
filter!(x => x.type != Whitespace).
|
||||
map!"a.text".
|
||||
equal(["0a", "1", "2"]));
|
||||
}
|
||||
assertThrown!TokenizeException("0".Tokenize!(TokenType, string));
|
||||
}
|
||||
|
||||
/// Tokenizes the src into the TokenType.
|
||||
/// Returns: an input range of Token!(TokenType, S) as a voldemorte type
|
||||
auto Tokenize(TokenType, S)(S src)
|
||||
if (is(TokenType == enum) && isSomeString!S) {
|
||||
return Tokenizer!(TokenType, S)(src).drop(1);
|
||||
}
|
||||
|
||||
private struct Tokenizer(TokenType_, S)
|
||||
if (is(TokenType_ == enum) && isSomeString!S) {
|
||||
public:
|
||||
alias TokenType = TokenType_;
|
||||
alias Token = dast.tokenize.data.Token!(TokenType, S);
|
||||
|
||||
void PopFront() in (!empty) {
|
||||
if (cursor_ < src.length) {
|
||||
TokenizeNext();
|
||||
} else {
|
||||
end_ = true;
|
||||
}
|
||||
}
|
||||
alias popFront = PopFront;
|
||||
|
||||
@property Token front() const in (!empty) {
|
||||
return last_tokenized_;
|
||||
}
|
||||
@property bool empty() const {
|
||||
return end_;
|
||||
}
|
||||
|
||||
const S src;
|
||||
|
||||
private:
|
||||
void TokenizeNext() in (!empty) {
|
||||
last_tokenized_.pos.stline = line_;
|
||||
last_tokenized_.pos.stchar = char_;
|
||||
|
||||
const match = FindMatchedTokenTypes!TokenType(src[cursor_..$]);
|
||||
if (match.types.length == 0 || match.length == 0) {
|
||||
throw new TokenizeException(
|
||||
"found the uncategorizable token",
|
||||
line_, char_, src[cursor_..cursor_+1]);
|
||||
}
|
||||
if (match.types.length > 1) {
|
||||
throw new TokenizeException(
|
||||
"found the token which can be categorizable to multiple types %s".
|
||||
format(match.types),
|
||||
line_, char_, src[cursor_..cursor_+1]);
|
||||
}
|
||||
|
||||
last_tokenized_.text = src[cursor_..cursor_+match.length];
|
||||
last_tokenized_.type = match.types[0];
|
||||
|
||||
foreach (c; last_tokenized_.text) {
|
||||
++char_;
|
||||
if (c == '\n') {
|
||||
++line_;
|
||||
char_ = 0;
|
||||
}
|
||||
}
|
||||
cursor_ += match.length;
|
||||
|
||||
last_tokenized_.pos.edline = line_;
|
||||
last_tokenized_.pos.edchar = char_;
|
||||
}
|
||||
|
||||
Token last_tokenized_;
|
||||
|
||||
bool end_;
|
||||
|
||||
size_t cursor_;
|
||||
size_t line_, char_;
|
||||
}
|
19
thirdparty/dast/src/dast/util/range.d
vendored
Normal file
19
thirdparty/dast/src/dast/util/range.d
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/// License: MIT
|
||||
module dast.util.range;
|
||||
|
||||
import std.algorithm,
|
||||
std.array,
|
||||
std.range.primitives;
|
||||
|
||||
/// Returns: an input range which has unique items
|
||||
auto DropDuplicated(R)(R src) if (isInputRange!R) {
|
||||
auto dest = appender!(ElementType!R[]);
|
||||
dest.reserve(src.length);
|
||||
|
||||
foreach (item; src) if (!dest[].canFind(item)) dest ~= item;
|
||||
return dest[];
|
||||
}
|
||||
///
|
||||
unittest {
|
||||
static assert([0, 1, 0, 1].DropDuplicated.equal([0, 1]));
|
||||
}
|
15
thirdparty/dast/test/all.sh
vendored
Normal file
15
thirdparty/dast/test/all.sh
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ ! -d ".git" ]; then
|
||||
echo "plz run at root of this repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function test() {
|
||||
result=`$1 "$2"`
|
||||
if [ "$result" != "$3" ]; then
|
||||
echo "\`$1 \"$2\"\` != \`$3\`"
|
||||
fi
|
||||
}
|
||||
|
||||
test ./test/math.d "1+2-3*4/5" "1"
|
91
thirdparty/dast/test/math.d
vendored
Normal file
91
thirdparty/dast/test/math.d
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env dub
|
||||
/+ dub.json:
|
||||
{
|
||||
"name": "math",
|
||||
"dependencies": {
|
||||
"dast": {"path": "../"}
|
||||
}
|
||||
} +/
|
||||
|
||||
import std;
|
||||
import dast.parse,
|
||||
dast.tokenize;
|
||||
|
||||
enum TokenType {
|
||||
@TextAllMatcher!isDigit Number,
|
||||
|
||||
@TextCompleteMatcher!"+" Add,
|
||||
@TextCompleteMatcher!"-" Sub,
|
||||
@TextCompleteMatcher!"*" Mul,
|
||||
@TextCompleteMatcher!"/" Div,
|
||||
|
||||
@TextCompleteMatcher!"(" OpenParen,
|
||||
@TextCompleteMatcher!")" CloseParen,
|
||||
|
||||
End,
|
||||
}
|
||||
alias Token = dast.tokenize.Token!(TokenType, string);
|
||||
|
||||
struct Whole {
|
||||
int result;
|
||||
}
|
||||
struct TermList {
|
||||
int value;
|
||||
}
|
||||
struct Term {
|
||||
int value;
|
||||
}
|
||||
|
||||
class RuleSet {
|
||||
public:
|
||||
@ParseRule:
|
||||
static Whole ParseWhole(TermList terms, @(TokenType.End) Token) {
|
||||
return Whole(terms.value);
|
||||
}
|
||||
|
||||
static TermList ParseTermListFromAddedNextTerm(
|
||||
TermList lterms, @(TokenType.Add) Token, Term term) {
|
||||
return TermList(lterms.value + term.value);
|
||||
}
|
||||
static TermList ParseTermListFromSubtractedNextTerm(
|
||||
TermList lterms, @(TokenType.Sub) Token, Term term) {
|
||||
return TermList(lterms.value - term.value);
|
||||
}
|
||||
static TermList ParseTermListFirstItem(Term term) {
|
||||
return TermList(term.value);
|
||||
}
|
||||
|
||||
static Term ParseTermFromFirstNumber(@(TokenType.Number) Token num) {
|
||||
return Term(num.text.to!int);
|
||||
}
|
||||
|
||||
static Term ParseTermFromTermList(
|
||||
@(TokenType.OpenParen) Token, TermList terms, @(TokenType.CloseParen) Token) {
|
||||
return Term(terms.value);
|
||||
}
|
||||
static Term ParseMultipledTerm(
|
||||
Term lterm, @(TokenType.Mul) Token, @(TokenType.Number) Token num) {
|
||||
return Term(lterm.value * num.text.to!int);
|
||||
}
|
||||
static Term ParseDividedTerm(
|
||||
Term lterm, @(TokenType.Div) Token, @(TokenType.Number) Token num) {
|
||||
return Term(lterm.value / num.text.to!int);
|
||||
}
|
||||
}
|
||||
|
||||
void main(string[] args) {
|
||||
assert(args.length == 2);
|
||||
|
||||
// PrintItemSet!(TokenType, RuleSet, Whole);
|
||||
|
||||
try {
|
||||
args[1].
|
||||
Tokenize!TokenType.
|
||||
chain([Token("", TokenType.End)]).
|
||||
Parse!Whole(cast(RuleSet) null).
|
||||
result.writeln;
|
||||
} catch (ParseException!Token e) {
|
||||
"%s at token '%s' [%s] at (%d, %d)".
|
||||
writefln(e.msg, e.token.text, e.token.type, e.token.pos.stline, e.token.pos.stchar);
|
||||
}
|
||||
}
|
1
thirdparty/ft4d
vendored
1
thirdparty/ft4d
vendored
@ -1 +0,0 @@
|
||||
Subproject commit 791af0dfe67ad25b41aae170568b89303ed853e9
|
5
thirdparty/ft4d/.gitignore
vendored
Normal file
5
thirdparty/ft4d/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/.bin
|
||||
/.dub
|
||||
/dub.selections.json
|
||||
|
||||
*.swp
|
9
thirdparty/ft4d/LICENSE
vendored
Normal file
9
thirdparty/ft4d/LICENSE
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 KajiroNagi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
14
thirdparty/ft4d/dub.json
vendored
Normal file
14
thirdparty/ft4d/dub.json
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "ft4d",
|
||||
"license": "Boost",
|
||||
|
||||
"targetType": "library",
|
||||
"targetPath": ".bin",
|
||||
|
||||
"dependencies": {
|
||||
"bindbc-freetype": "~>0.7.0"
|
||||
},
|
||||
|
||||
"versions": ["BindFT_Static"],
|
||||
"libs": ["freetype"]
|
||||
}
|
100
thirdparty/ft4d/src/ft4d/Face.d
vendored
Normal file
100
thirdparty/ft4d/src/ft4d/Face.d
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
// License: MIT
|
||||
module ft4d.Face;
|
||||
|
||||
import std.conv,
|
||||
std.exception,
|
||||
std.string,
|
||||
std.typecons;
|
||||
|
||||
import ft4d.ft;
|
||||
|
||||
/// RefCounted version of Face.
|
||||
alias FaceRef = RefCounted!Face;
|
||||
|
||||
/// A wrapper type for freetype Face object.
|
||||
///
|
||||
/// Usually this is wrapped by RefCounted.
|
||||
/// When it's default, empty() property returns true.
|
||||
struct Face {
|
||||
public:
|
||||
@disable this(this);
|
||||
|
||||
alias native this;
|
||||
|
||||
/// Creates new face from the path.
|
||||
static FaceRef CreateFromPath(string path, int index = 0) {
|
||||
FT_Face f;
|
||||
FT_New_Face(ft.lib, path.toStringz, index.to!FT_Long, &f).
|
||||
EnforceFT();
|
||||
return FaceRef(f);
|
||||
}
|
||||
/// Creates new face from the buffer.
|
||||
static FaceRef CreateFromBuffer(in ubyte[] buf, int index = 0) {
|
||||
FT_Face f;
|
||||
FT_New_Memory_Face(ft.lib, buf.ptr, buf.length.to!FT_Long, index.to!FT_Long, &f).
|
||||
EnforceFT();
|
||||
return FaceRef(f);
|
||||
}
|
||||
|
||||
~this() {
|
||||
if (!empty) FT_Done_Face(native_).EnforceFT();
|
||||
}
|
||||
|
||||
/// A move operator. RHS will be empty.
|
||||
ref Face opAssign(ref Face rhs) {
|
||||
if (&rhs != &this) {
|
||||
native_ = rhs.native_;
|
||||
rhs.native_ = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
@property bool empty() const {
|
||||
return !native_;
|
||||
}
|
||||
/// You should not modify the pointer directly.
|
||||
@property inout(FT_Face) native() inout in (!empty) {
|
||||
return native_;
|
||||
}
|
||||
|
||||
private:
|
||||
FT_Face native_;
|
||||
}
|
||||
|
||||
/// A set of parameters for loading glyphs.
|
||||
struct GlyphLoader {
|
||||
public:
|
||||
///
|
||||
int pxWidth, pxHeight;
|
||||
///
|
||||
dchar character;
|
||||
|
||||
///
|
||||
FT_Int32 flags = FT_LOAD_DEFAULT;
|
||||
|
||||
/// Loads a glyph with the parameters this has.
|
||||
bool Load(ref FaceRef face) const
|
||||
in {
|
||||
assert(!face.empty);
|
||||
assert(pxWidth+pxHeight > 0 && pxWidth >= 0 && pxHeight >= 0);
|
||||
}
|
||||
do {
|
||||
const i = FT_Get_Char_Index(face, character.to!FT_ULong);
|
||||
if (i == 0) return false;
|
||||
|
||||
FT_Set_Pixel_Sizes(face, pxWidth.to!FT_UInt, pxHeight.to!FT_UInt).EnforceFT();
|
||||
|
||||
FT_Load_Glyph(face, i, flags).EnforceFT();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Throws an exception if the face doesn't have a rendered bitmap in its glyph slot.
|
||||
///
|
||||
/// Returns: the rendered bitmap in the glyph slot
|
||||
ref const(FT_Bitmap) EnforceGlyphBitmap(in ref FaceRef face) in (!face.empty) {
|
||||
(face.glyph.format == FT_GLYPH_FORMAT_BITMAP).
|
||||
enforce("the glyph doesn't have bitmap");
|
||||
return face.glyph.bitmap;
|
||||
}
|
40
thirdparty/ft4d/src/ft4d/ft.d
vendored
Normal file
40
thirdparty/ft4d/src/ft4d/ft.d
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
// License: MIT
|
||||
module ft4d.ft;
|
||||
|
||||
import std.exception;
|
||||
|
||||
public import bindbc.freetype;
|
||||
|
||||
/// This class is just for separating ft functions from global namespace.
|
||||
abstract class ft {
|
||||
public:
|
||||
/// Initializes freetype library.
|
||||
static void Initialize() {
|
||||
if (lib_) return;
|
||||
FT_Init_FreeType(&lib_).EnforceFT();
|
||||
}
|
||||
/// Disposes all resources allocated by freetype library.
|
||||
static void Dispose() {
|
||||
if (!lib_) return;
|
||||
FT_Done_FreeType(lib_).EnforceFT();
|
||||
}
|
||||
/// Checks if freetype library has already been initialized
|
||||
static @property bool IsInitialized() {
|
||||
return !!lib_;
|
||||
}
|
||||
|
||||
/// Returns: a pointer to an initialized library
|
||||
static @property FT_Library lib() in (IsInitialized) {
|
||||
return lib_;
|
||||
}
|
||||
|
||||
private:
|
||||
static FT_Library lib_;
|
||||
}
|
||||
|
||||
/// Checks the result value of freetype functions and throws an exception if needed.
|
||||
void EnforceFT(FT_Error i) {
|
||||
// TODO: throw with descriptions
|
||||
|
||||
(i == FT_Err_Ok).enforce("unknown error");
|
||||
}
|
7
thirdparty/ft4d/src/ft4d/package.d
vendored
Normal file
7
thirdparty/ft4d/src/ft4d/package.d
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// License: MIT
|
||||
module ft4d;
|
||||
|
||||
public {
|
||||
import ft4d.ft,
|
||||
ft4d.Face;
|
||||
}
|
32
thirdparty/ft4d/test.d
vendored
Normal file
32
thirdparty/ft4d/test.d
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env dub
|
||||
|
||||
/+ dub.json:
|
||||
{
|
||||
"name": "test",
|
||||
|
||||
"dependencies": {
|
||||
"ft4d": {"path": "."}
|
||||
}
|
||||
}
|
||||
+/
|
||||
|
||||
import std;
|
||||
import ft4d;
|
||||
|
||||
void main() {
|
||||
ft.Initialize();
|
||||
assert(ft.IsInitialized);
|
||||
scope(exit) ft.Dispose();
|
||||
|
||||
auto face = Face.CreateFromPath("/usr/share/fonts/TTF/Ricty-Regular.ttf");
|
||||
|
||||
GlyphLoader loader;
|
||||
loader.pxWidth = 16;
|
||||
loader.pxHeight = 0;
|
||||
loader.flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER;
|
||||
|
||||
loader.character = 'a';
|
||||
loader.Load(face).enforce();
|
||||
|
||||
face.EnforceGlyphBitmap().writeln;
|
||||
}
|
1
thirdparty/gl4d
vendored
1
thirdparty/gl4d
vendored
@ -1 +0,0 @@
|
||||
Subproject commit 33883a68a9179cdfca72da9d0f60a88e1f385715
|
8
thirdparty/gl4d/.gitignore
vendored
Normal file
8
thirdparty/gl4d/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/.bin
|
||||
/.dub
|
||||
/dub.selections.json
|
||||
|
||||
/docs
|
||||
/docs.json
|
||||
|
||||
*.swp
|
13
thirdparty/gl4d/dub.json
vendored
Normal file
13
thirdparty/gl4d/dub.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "gl4d",
|
||||
"license": "MIT",
|
||||
|
||||
"targetType": "library",
|
||||
"targetPath": ".bin",
|
||||
|
||||
"dependencies": {
|
||||
"bindbc-opengl": "~>0.9.0",
|
||||
"gl3n": "~>1.3.1"
|
||||
},
|
||||
"versions": ["GL_33"]
|
||||
}
|
164
thirdparty/gl4d/src/gl4d/Buffer.d
vendored
Normal file
164
thirdparty/gl4d/src/gl4d/Buffer.d
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
/// License: MIT
|
||||
module gl4d.Buffer;
|
||||
|
||||
import std.conv,
|
||||
std.typecons;
|
||||
|
||||
import gl4d.gl,
|
||||
gl4d.math,
|
||||
gl4d.GLObject;
|
||||
|
||||
///
|
||||
alias ArrayBuffer = Buffer!GL_ARRAY_BUFFER;
|
||||
/// RefCounted version of ArrayBuffer.
|
||||
alias ArrayBufferRef = BufferRef!GL_ARRAY_BUFFER;
|
||||
///
|
||||
alias ArrayBufferAllocator = BufferAllocator!GL_ARRAY_BUFFER;
|
||||
///
|
||||
alias ArrayBufferOverwriter = BufferOverwriter!GL_ARRAY_BUFFER;
|
||||
|
||||
///
|
||||
alias ElementArrayBuffer = Buffer!GL_ELEMENT_ARRAY_BUFFER;
|
||||
/// RefCounted version of ElementArrayBuffer.
|
||||
alias ElementArrayBufferRef = BufferRef!GL_ELEMENT_ARRAY_BUFFER;
|
||||
///
|
||||
alias ElementArrayBufferAllocator = BufferAllocator!GL_ELEMENT_ARRAY_BUFFER;
|
||||
///
|
||||
alias ElementArrayBufferOverwriter = BufferOverwriter!GL_ELEMENT_ARRAY_BUFFER;
|
||||
|
||||
///
|
||||
alias UniformBuffer = Buffer!GL_UNIFORM_BUFFER;
|
||||
/// RefCounted version of UniformBuffer.
|
||||
alias UniformBufferRef = BufferRef!GL_UNIFORM_BUFFER;
|
||||
///
|
||||
alias UniformBufferAllocator = BufferAllocator!GL_UNIFORM_BUFFER;
|
||||
///
|
||||
alias UniformBufferOverwriter = BufferOverwriter!GL_UNIFORM_BUFFER;
|
||||
|
||||
/// RefCounted version of Buffer.
|
||||
template BufferRef(GLenum target) {
|
||||
alias BufferRef = RefCounted!(Buffer!target);
|
||||
}
|
||||
|
||||
/// A wrapper type for OpenGL buffer.
|
||||
///
|
||||
/// Usually this is wrapped by RefCounted.
|
||||
/// When it's in default, empty() property returns true and id() property is invalid.
|
||||
struct Buffer(GLenum target_) {
|
||||
mixin GLObject!(
|
||||
(x, y) => gl.GenBuffers(x, y),
|
||||
(x) => gl.BindBuffer(target_, x),
|
||||
(x) => gl.DeleteTextures(1, x)
|
||||
);
|
||||
|
||||
public:
|
||||
///
|
||||
enum target = target_;
|
||||
|
||||
/// Binds this buffer to be written by transform feedbacks.
|
||||
static if (target_ == GL_ARRAY_BUFFER)
|
||||
void BindForTransformFeedback(int index) {
|
||||
assert(!empty);
|
||||
gl.BindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, index.to!GLuint, id);
|
||||
}
|
||||
|
||||
/// Binds this buffer to the uniform block.
|
||||
static if (target_ == GL_UNIFORM_BUFFER)
|
||||
void BindForUniformBlock(int index) {
|
||||
assert(!empty);
|
||||
gl.BindBufferBase(GL_UNIFORM_BUFFER, index.to!GLuint, id);
|
||||
}
|
||||
}
|
||||
|
||||
/// An allocator for buffers.
|
||||
struct BufferAllocator(GLenum target) {
|
||||
public:
|
||||
///
|
||||
size_t size;
|
||||
///
|
||||
const(void)* data;
|
||||
///
|
||||
GLenum usage = GL_STATIC_DRAW;
|
||||
|
||||
/// Allocates the buffer with parameters this has.
|
||||
///
|
||||
/// Binds the buffer automatically.
|
||||
void Allocate(ref BufferRef!target buffer)
|
||||
in {
|
||||
assert(!buffer.empty);
|
||||
assert(size > 0);
|
||||
}
|
||||
do {
|
||||
buffer.Bind();
|
||||
gl.BufferData(target, size.to!GLsizeiptr, data, usage);
|
||||
}
|
||||
}
|
||||
|
||||
/// An overwriter for buffers.
|
||||
struct BufferOverwriter(GLenum target) {
|
||||
public:
|
||||
///
|
||||
size_t offset;
|
||||
///
|
||||
size_t size;
|
||||
///
|
||||
const(void)* data;
|
||||
|
||||
/// Overwrites the buffer with parameters this has.
|
||||
///
|
||||
/// The buffer will be bound automatically.
|
||||
void Overwrite(ref BufferRef!target buffer)
|
||||
in {
|
||||
assert(!buffer.empty);
|
||||
|
||||
assert(offset >= 0);
|
||||
assert(size > 0);
|
||||
}
|
||||
do {
|
||||
buffer.Bind();
|
||||
gl.BufferSubData(target, offset.to!GLintptr, size.to!GLsizeiptr, data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes the buffer's data pointer.
|
||||
///
|
||||
/// T must be a BufferRef type.
|
||||
///
|
||||
/// The buffer will be bound automatically.
|
||||
///
|
||||
/// Escaping its scope, the pointer will be disabled automatically.
|
||||
/// Returns: a voldemorte type which can behave as same as PtrT*
|
||||
auto MapToRead(PtrT = void, T)(ref T buf) {
|
||||
return buf.Map!(PtrT, T.target, GL_READ_ONLY);
|
||||
}
|
||||
/// ditto
|
||||
auto MapToWrite(PtrT = void, T)(ref T buf) {
|
||||
return buf.Map!(PtrT, T.target, GL_WRITE_ONLY);
|
||||
}
|
||||
/// ditto
|
||||
auto MapToReadWrite(PtrT = void, T)(ref T buf) {
|
||||
return buf.Map!(PtrT, T.target, GL_READ_WRITE);
|
||||
}
|
||||
private auto Map(PtrT, GLenum target, GLenum usage)(ref BufferRef!target buf) {
|
||||
assert(!buf.empty);
|
||||
|
||||
buf.Bind();
|
||||
auto ptr = gl.MapBuffer(target, usage);
|
||||
|
||||
struct Mapper {
|
||||
public:
|
||||
@disable this(this);
|
||||
|
||||
~this() {
|
||||
gl.UnmapBuffer(target);
|
||||
}
|
||||
|
||||
static if (usage == GL_READ_ONLY) {
|
||||
@property const(PtrT*) entity() const return { return cast(PtrT*) ptr; }
|
||||
} else {
|
||||
@property inout(PtrT*) entity() inout return { return cast(PtrT*) ptr; }
|
||||
}
|
||||
alias entity this;
|
||||
}
|
||||
return Mapper();
|
||||
}
|
95
thirdparty/gl4d/src/gl4d/Framebuffer.d
vendored
Normal file
95
thirdparty/gl4d/src/gl4d/Framebuffer.d
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
/// License: MIT
|
||||
module gl4d.Framebuffer;
|
||||
|
||||
import std.conv,
|
||||
std.exception,
|
||||
std.typecons,
|
||||
std.variant;
|
||||
|
||||
import gl4d.gl,
|
||||
gl4d.math,
|
||||
gl4d.GLObject,
|
||||
gl4d.Renderbuffer,
|
||||
gl4d.Texture;
|
||||
|
||||
/// RefCounted version of OpenGL framebuffer.
|
||||
alias FramebufferRef = RefCounted!Framebuffer;
|
||||
|
||||
/// A variant type of types which can be framebuffers' attachments.
|
||||
alias FramebufferAttachment = Algebraic!(
|
||||
Texture2DRef,
|
||||
TextureRectRef,
|
||||
RenderbufferRef
|
||||
);
|
||||
|
||||
/// A wrapper type for OpenGL framebuffer.
|
||||
///
|
||||
/// Usually this is wrapped by RefCounted.
|
||||
/// When it's in default, empty() property returns true and id() property is invalid.
|
||||
struct Framebuffer {
|
||||
mixin GLObject!(
|
||||
(x, y) => gl.GenFramebuffers(x, y),
|
||||
(x) => gl.BindFramebuffer(GL_FRAMEBUFFER, x),
|
||||
(x) => gl.DeleteFramebuffers(1, x)
|
||||
);
|
||||
|
||||
public:
|
||||
~this() {
|
||||
// Forces unrefering all buffers.
|
||||
foreach (key; attachments_.keys) {
|
||||
attachments_[key] = FramebufferAttachment.init;
|
||||
}
|
||||
}
|
||||
|
||||
/// Binds this framebuffer to be read.
|
||||
void BindToRead() {
|
||||
gl.BindFramebuffer(GL_READ_FRAMEBUFFER, id);
|
||||
}
|
||||
/// Binds this framebuffer to be written.
|
||||
void BindToWrite() {
|
||||
gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, id);
|
||||
}
|
||||
|
||||
/// Validates this framebuffer.
|
||||
///
|
||||
/// This framebuffer must be bound to be written.
|
||||
void Validate() {
|
||||
const status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
(status == GL_FRAMEBUFFER_COMPLETE).
|
||||
enforce("The framebuffer validation failed.");
|
||||
}
|
||||
|
||||
private:
|
||||
FramebufferAttachment[GLenum] attachments_;
|
||||
}
|
||||
|
||||
/// Attaches the buffer as the attachment to the framebuffer.
|
||||
///
|
||||
/// The framebuffer must be bound to be written.
|
||||
@property void attachment(GLenum type)(
|
||||
ref FramebufferRef fb, ref RenderbufferRef buf) {
|
||||
assert(!fb.empty);
|
||||
assert(!buf.empty);
|
||||
|
||||
fb.attachments_[type] = buf;
|
||||
gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, type, GL_RENDERBUFFER, buf.id);
|
||||
}
|
||||
/// ditto
|
||||
@property void attachment(GLenum type, int miplvl = 0, GLenum target)(
|
||||
ref FramebufferRef fb, ref TextureRef!target buf)
|
||||
if (target.IsSupported2DTextureTarget) {
|
||||
assert(!fb.empty);
|
||||
assert(!buf.empty);
|
||||
|
||||
fb.attachments_[type] = buf;
|
||||
gl.FramebufferTexture2D(GL_FRAMEBUFFER,
|
||||
type, target, buf.id, miplvl.to!GLint);
|
||||
}
|
||||
|
||||
/// Changes color attachments' order.
|
||||
///
|
||||
/// The framebuffer must be bound to be written.
|
||||
@property void attachmentOrder(ref FramebufferRef fb, GLenum[] attachments) {
|
||||
assert(!fb.empty);
|
||||
gl.DrawBuffers(attachments.length.to!GLsizei, attachments.ptr);
|
||||
}
|
56
thirdparty/gl4d/src/gl4d/GLObject.d
vendored
Normal file
56
thirdparty/gl4d/src/gl4d/GLObject.d
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
/// License: MIT
|
||||
module gl4d.GLObject;
|
||||
|
||||
import gl4d.gl;
|
||||
|
||||
/// A template for OpenGL objects' wrapper types.
|
||||
mixin template GLObject(alias generator, alias binder, alias deleter) {
|
||||
import std.algorithm,
|
||||
std.array,
|
||||
std.typecons;
|
||||
|
||||
public:
|
||||
@disable this(this);
|
||||
|
||||
/// Creates a single object.
|
||||
static RefCounted!This Create() {
|
||||
GLuint id;
|
||||
generator(1, &id);
|
||||
return RefCounted!This(id);
|
||||
}
|
||||
/// Creates multiple objects.
|
||||
static RefCounted!This[] Create(int count) {
|
||||
assert(count > 0);
|
||||
auto id = new GLuint[count];
|
||||
generator(count, id.ptr);
|
||||
return id.map!(i => RefCounted!This(i)).array;
|
||||
}
|
||||
|
||||
~this() {
|
||||
if (!empty) deleter(&id);
|
||||
}
|
||||
|
||||
static if (is(typeof(() => binder(0)))) {
|
||||
/// Binds this object.
|
||||
void Bind() {
|
||||
assert(!empty);
|
||||
binder(id);
|
||||
}
|
||||
/// Unbinds this object
|
||||
void Unbind() {
|
||||
assert(!empty);
|
||||
binder(0);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
@property bool empty() const {
|
||||
return id == 0;
|
||||
}
|
||||
|
||||
///
|
||||
const GLuint id;
|
||||
|
||||
private:
|
||||
alias This = typeof(this);
|
||||
}
|
204
thirdparty/gl4d/src/gl4d/Program.d
vendored
Normal file
204
thirdparty/gl4d/src/gl4d/Program.d
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
/// License: MIT
|
||||
module gl4d.Program;
|
||||
|
||||
import std.algorithm,
|
||||
std.array,
|
||||
std.conv,
|
||||
std.exception,
|
||||
std.string,
|
||||
std.typecons;
|
||||
|
||||
import gl4d.gl,
|
||||
gl4d.math,
|
||||
gl4d.Shader;
|
||||
|
||||
/// Whether geometry shaders are available.
|
||||
enum IsGeometryShaderAvailable = (glSupport >= GLSupport.gl40);
|
||||
|
||||
/// RefCounted version of OpenGL program.
|
||||
alias ProgramRef = RefCounted!Program;
|
||||
|
||||
/// A wrapper type for OpenGL program.
|
||||
///
|
||||
/// Usually this is wrapped by RefCounted.
|
||||
/// When it's in default, empty() property returns true and id() property is invalid.
|
||||
struct Program {
|
||||
public:
|
||||
@disable this(this);
|
||||
|
||||
///
|
||||
this(GLuint id) {
|
||||
this.id = id;
|
||||
|
||||
assert(!empty);
|
||||
(Get!GL_LINK_STATUS == GL_TRUE).enforce(log);
|
||||
}
|
||||
~this() {
|
||||
if (!empty) gl.DeleteProgram(id);
|
||||
}
|
||||
|
||||
/// Makes this program current.
|
||||
void Use() {
|
||||
assert(!empty);
|
||||
gl.UseProgram(id);
|
||||
}
|
||||
/// Makes this program incurrent.
|
||||
void Unuse() {
|
||||
assert(!empty);
|
||||
gl.UseProgram(0);
|
||||
}
|
||||
|
||||
/// Validates this program.
|
||||
///
|
||||
/// If it's failed, throws an exception with log.
|
||||
void Validate() const {
|
||||
gl.ValidateProgram(id);
|
||||
(Get!GL_VALIDATE_STATUS == GL_TRUE).enforce(log);
|
||||
}
|
||||
|
||||
/// This may takes too long time.
|
||||
@property string log() const {
|
||||
assert(!empty);
|
||||
|
||||
const len = Get!GL_INFO_LOG_LENGTH;
|
||||
|
||||
auto msg = new char[len];
|
||||
gl.GetProgramInfoLog(id, len, null, msg.ptr);
|
||||
return msg.to!string;
|
||||
}
|
||||
|
||||
///
|
||||
@property bool empty() const {
|
||||
return id == 0;
|
||||
}
|
||||
|
||||
///
|
||||
const GLuint id;
|
||||
|
||||
private:
|
||||
// Be carefully, this may take too long time.
|
||||
GLint Get(GLenum param)() const {
|
||||
GLint temp = void;
|
||||
gl.GetProgramiv(id, param, &temp);
|
||||
return temp;
|
||||
}
|
||||
|
||||
VertexShaderRef vertex_;
|
||||
|
||||
static if (IsGeometryShaderAvailable)
|
||||
GeometryShaderRef geometry_;
|
||||
|
||||
FragmentShaderRef fragment_;
|
||||
}
|
||||
|
||||
/// A linker for OpenGL program.
|
||||
struct ProgramLinker {
|
||||
public:
|
||||
///
|
||||
VertexShaderRef vertex;
|
||||
|
||||
static if (IsGeometryShaderAvailable) {
|
||||
///
|
||||
GeometryShaderRef geometry;
|
||||
///
|
||||
int geometryOutputVertices = 1024;
|
||||
///
|
||||
GLenum geometryInputType = GL_POINTS;
|
||||
///
|
||||
GLenum geometryOutputType = GL_POINTS;
|
||||
}
|
||||
|
||||
///
|
||||
FragmentShaderRef fragment;
|
||||
|
||||
///
|
||||
string[] feedbackVaryings;
|
||||
///
|
||||
bool feedbackInterleaved;
|
||||
|
||||
/// Creates new program with parameters this has.
|
||||
ProgramRef Link()
|
||||
in {
|
||||
assert(!vertex.empty);
|
||||
assert(!fragment.empty);
|
||||
}
|
||||
do {
|
||||
const id = gl.CreateProgram();
|
||||
|
||||
gl.AttachShader(id, vertex.id);
|
||||
scope(exit) vertex = vertex.init;
|
||||
|
||||
gl.AttachShader(id, fragment.id);
|
||||
scope(exit) fragment = fragment.init;
|
||||
|
||||
static if (IsGeometryShaderAvailable) if (!geometry.empty) {
|
||||
gl.AttachShader(id, geometry.id);
|
||||
scope(exit) geometry = geometry.init;
|
||||
|
||||
static if (glSupport >= GLSupport.gl40) {
|
||||
gl.ProgramParameteri(id, GL_GEOMETRY_VERTICES_OUT, geometryOutputVertices);
|
||||
|
||||
gl.ProgramParameteri(id, GL_GEOMETRY_INPUT_TYPE, geometryInputType);
|
||||
gl.ProgramParameteri(id, GL_GEOMETRY_OUTPUT_TYPE, geometryOutputType);
|
||||
}
|
||||
}
|
||||
|
||||
if (feedbackVaryings.length > 0) {
|
||||
const varys = feedbackVaryings.map!toStringz.array;
|
||||
gl.TransformFeedbackVaryings(id,
|
||||
feedbackVaryings.length.to!GLsizei, cast(char**) varys.ptr,
|
||||
feedbackInterleaved? GL_INTERLEAVED_ATTRIBS: GL_SEPARATE_ATTRIBS);
|
||||
}
|
||||
|
||||
gl.LinkProgram(id);
|
||||
return ProgramRef(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets uniform values with the data. (The program must be current.)
|
||||
///
|
||||
/// The program must be current.
|
||||
@property void uniform(int loc, T)(ref ProgramRef program, T data) {
|
||||
assert(!program.empty);
|
||||
|
||||
static if (is(T == int)) {
|
||||
gl.Uniform1i(loc, data);
|
||||
} else static if (is(T == vec2i)) {
|
||||
gl.Uniform2i(loc, data.x, data.y);
|
||||
} else static if (is(T == vec3i)) {
|
||||
gl.Uniform3i(loc, data.x, data.y, data.z);
|
||||
} else static if (is(T == vec4i)) {
|
||||
gl.Uniform4i(loc, data.x, data.y, data.z, data.w);
|
||||
} else static if (is(T == float)) {
|
||||
gl.Uniform1f(loc, data);
|
||||
} else static if (is(T == vec2)) {
|
||||
gl.Uniform2f(loc, data.x, data.y);
|
||||
} else static if (is(T == vec3)) {
|
||||
gl.Uniform3f(loc, data.x, data.y, data.z);
|
||||
} else static if (is(T == vec4)) {
|
||||
gl.Uniform4f(loc, data.x, data.y, data.z, data.w);
|
||||
} else static if (is(T == mat2)) {
|
||||
gl.UniformMatrix2fv(loc, 1, true, &data[0][0]);
|
||||
} else static if (is(T == mat3)) {
|
||||
gl.UniformMatrix3fv(loc, 1, true, &data[0][0]);
|
||||
} else static if (is(T == mat4)) {
|
||||
gl.UniformMatrix4fv(loc, 1, true, &data[0][0]);
|
||||
} else {
|
||||
static assert(false);
|
||||
}
|
||||
}
|
||||
// Tests for the above template.
|
||||
static assert(is(typeof((ref ProgramRef program) => program.uniform!0 = 0)));
|
||||
static assert(is(typeof((ref ProgramRef program) => program.uniform!1 = vec4())));
|
||||
static assert(is(typeof((ref ProgramRef program) => program.uniform!2 = mat4())));
|
||||
|
||||
/// Numbers uniform blocks of the names.
|
||||
void NumberUniformBlocks(string[int] names)(ref ProgramRef program) {
|
||||
assert(!program.empty);
|
||||
|
||||
GLuint ubi = void;
|
||||
static foreach (i, name; names) {
|
||||
ubi = gl.GetUniformBlockIndex(program.id, name.toStringz);
|
||||
gl.UniformBlockBinding(program.id, ubi, i.to!GLuint);
|
||||
}
|
||||
}
|
46
thirdparty/gl4d/src/gl4d/Renderbuffer.d
vendored
Normal file
46
thirdparty/gl4d/src/gl4d/Renderbuffer.d
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
/// License: MIT
|
||||
module gl4d.Renderbuffer;
|
||||
|
||||
import std.conv,
|
||||
std.typecons;
|
||||
|
||||
import gl4d.gl,
|
||||
gl4d.math,
|
||||
gl4d.GLObject;
|
||||
|
||||
/// RefCounted version of OpenGL renderbuffer.
|
||||
alias RenderbufferRef = RefCounted!Renderbuffer;
|
||||
|
||||
/// A wrapper type for OpenGL renderbuffer.
|
||||
///
|
||||
/// Usually this is wrapped by RefCounted.
|
||||
/// When it's in default, empty() property returns true and id() property is invalid.
|
||||
struct Renderbuffer {
|
||||
mixin GLObject!(
|
||||
(x, y) => gl.GenRenderbuffers(x, y),
|
||||
(x) => gl.BindRenderbuffer(GL_RENDERBUFFER, x),
|
||||
(x) => gl.DeleteRenderbuffers(1, x)
|
||||
);
|
||||
}
|
||||
|
||||
/// An allocator for OpenGL renderbuffers.
|
||||
struct RenderbufferAllocator {
|
||||
public:
|
||||
///
|
||||
GLenum format;
|
||||
///
|
||||
vec2i size;
|
||||
|
||||
/// Allocates the renderbuffer's storage with parameters this has.
|
||||
///
|
||||
/// The renderbuffer will be bound automatically.
|
||||
void Allocate(ref RenderbufferRef rb)
|
||||
in {
|
||||
assert(!rb.empty);
|
||||
}
|
||||
do {
|
||||
rb.Bind();
|
||||
gl.RenderbufferStorage(GL_RENDERBUFFER,
|
||||
format, size.x.to!GLsizei, size.y.to!GLsizei);
|
||||
}
|
||||
}
|
57
thirdparty/gl4d/src/gl4d/Sampler.d
vendored
Normal file
57
thirdparty/gl4d/src/gl4d/Sampler.d
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/// License: MIT
|
||||
module gl4d.Sampler;
|
||||
|
||||
import std.conv,
|
||||
std.typecons;
|
||||
|
||||
import gl4d.gl,
|
||||
gl4d.GLObject;
|
||||
|
||||
/// RefCounted version of Sampler.
|
||||
alias SamplerRef = RefCounted!Sampler;
|
||||
|
||||
/// A wrapper type for OpenGL sampler.
|
||||
struct Sampler {
|
||||
mixin GLObject!(
|
||||
(x, y) => gl.GenSamplers(x, y),
|
||||
void,
|
||||
(x) => gl.DeleteSamplers(1, x)
|
||||
);
|
||||
|
||||
public:
|
||||
/// Binds to the texture unit index.
|
||||
void Bind(int index) {
|
||||
assert(!empty);
|
||||
gl.BindSampler(index.to!GLuint, id);
|
||||
}
|
||||
}
|
||||
|
||||
/// A configurer for OpenGL sampler.
|
||||
struct SamplerConfigurer {
|
||||
public:
|
||||
///
|
||||
GLenum wrapS = GL_CLAMP_TO_EDGE;
|
||||
///
|
||||
GLenum wrapT = GL_CLAMP_TO_EDGE;
|
||||
///
|
||||
GLenum wrapR = GL_CLAMP_TO_EDGE;
|
||||
|
||||
///
|
||||
GLenum filterMin = GL_NEAREST;
|
||||
///
|
||||
GLenum filterMag = GL_NEAREST;
|
||||
|
||||
/// Configures the sampler with parameters this has.
|
||||
void Configure(ref SamplerRef sampler)
|
||||
in {
|
||||
assert(!sampler.empty);
|
||||
}
|
||||
do {
|
||||
gl.SamplerParameteri(sampler.id, GL_TEXTURE_WRAP_S, wrapS);
|
||||
gl.SamplerParameteri(sampler.id, GL_TEXTURE_WRAP_T, wrapT);
|
||||
gl.SamplerParameteri(sampler.id, GL_TEXTURE_WRAP_R, wrapR);
|
||||
|
||||
gl.SamplerParameteri(sampler.id, GL_TEXTURE_MIN_FILTER, filterMin);
|
||||
gl.SamplerParameteri(sampler.id, GL_TEXTURE_MAG_FILTER, filterMag);
|
||||
}
|
||||
}
|
99
thirdparty/gl4d/src/gl4d/Shader.d
vendored
Normal file
99
thirdparty/gl4d/src/gl4d/Shader.d
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
/// License: MIT
|
||||
module gl4d.Shader;
|
||||
|
||||
import std.conv,
|
||||
std.exception,
|
||||
std.string,
|
||||
std.typecons;
|
||||
|
||||
import gl4d.gl;
|
||||
|
||||
/// A wrapper type for OpenGL vertex shader.
|
||||
///
|
||||
/// Usually this is wrapped by RefCounted.
|
||||
/// When it's in default, empty() property returns true and id() property is invalid.
|
||||
struct VertexShader {
|
||||
mixin Shader!GL_VERTEX_SHADER;
|
||||
}
|
||||
/// RefCounted version of VertexShader.
|
||||
alias VertexShaderRef = RefCounted!VertexShader;
|
||||
|
||||
/// A wrapper type for OpenGL geometry shader.
|
||||
///
|
||||
/// Usually this is wrapped by RefCounted.
|
||||
/// When it's in default, empty() property returns true and id() property is invalid.
|
||||
struct GeometryShader {
|
||||
mixin Shader!GL_GEOMETRY_SHADER;
|
||||
}
|
||||
/// RefCounted version of GeometryShader.
|
||||
alias GeometryShaderRef = RefCounted!GeometryShader;
|
||||
|
||||
/// A wrapper type for OpenGL fragment shader.
|
||||
///
|
||||
/// Usually this is wrapped by RefCounted.
|
||||
/// When it's in default, empty() property returns true and id() property is invalid.
|
||||
struct FragmentShader {
|
||||
mixin Shader!GL_FRAGMENT_SHADER;
|
||||
}
|
||||
/// RefCounted version of FragmentShader.
|
||||
alias FragmentShaderRef = RefCounted!FragmentShader;
|
||||
|
||||
/// A body of shader structures which cannot be refered from other modules.
|
||||
private mixin template Shader(GLenum type) {
|
||||
public:
|
||||
@disable this(this);
|
||||
|
||||
/// Creates new shader from the source with the type.
|
||||
static RefCounted!This Compile(string src) {
|
||||
const id = gl.CreateShader(type);
|
||||
|
||||
const srcptr = src.toStringz;
|
||||
gl.ShaderSource(id, 1, &srcptr, null);
|
||||
gl.CompileShader(id);
|
||||
|
||||
return RefCounted!This(id);
|
||||
}
|
||||
|
||||
///
|
||||
this(GLuint id) {
|
||||
this.id = id;
|
||||
|
||||
assert(!empty);
|
||||
(Get!GL_COMPILE_STATUS == GL_TRUE).enforce(log);
|
||||
}
|
||||
~this() {
|
||||
if (!empty) gl.DeleteShader(id);
|
||||
}
|
||||
|
||||
/// This may takes too long time.
|
||||
@property string log() const {
|
||||
assert(!empty);
|
||||
|
||||
const len = Get!GL_INFO_LOG_LENGTH;
|
||||
if (len == 0) return null;
|
||||
|
||||
auto msg = new char[len];
|
||||
gl.GetShaderInfoLog(id, len, null, msg.ptr);
|
||||
return msg.to!string;
|
||||
}
|
||||
|
||||
///
|
||||
@property bool empty() const {
|
||||
return id == 0;
|
||||
}
|
||||
|
||||
///
|
||||
const GLuint id;
|
||||
|
||||
private:
|
||||
alias This = typeof(this);
|
||||
|
||||
// Be carefully, this may take too long time.
|
||||
GLint Get(GLenum param)() const {
|
||||
assert(!empty);
|
||||
|
||||
GLint temp = void;
|
||||
gl.GetShaderiv(id, param, &temp);
|
||||
return temp;
|
||||
}
|
||||
}
|
146
thirdparty/gl4d/src/gl4d/Texture.d
vendored
Normal file
146
thirdparty/gl4d/src/gl4d/Texture.d
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
/// License: MIT
|
||||
module gl4d.Texture;
|
||||
|
||||
import std.conv,
|
||||
std.typecons;
|
||||
|
||||
import gl4d.gl,
|
||||
gl4d.math,
|
||||
gl4d.GLObject;
|
||||
|
||||
///
|
||||
alias Texture2D = Texture!GL_TEXTURE_2D;
|
||||
/// RefCounted version of Texture2D.
|
||||
alias Texture2DRef = TextureRef!GL_TEXTURE_2D;
|
||||
///
|
||||
alias Texture2DAllocator = TextureAllocator!GL_TEXTURE_2D;
|
||||
///
|
||||
alias Texture2DOverwriter = TextureOverwriter!GL_TEXTURE_2D;
|
||||
|
||||
///
|
||||
alias TextureRect = Texture!GL_TEXTURE_RECTANGLE;
|
||||
/// RefCounted version of TextureRect.
|
||||
alias TextureRectRef = TextureRef!GL_TEXTURE_RECTANGLE;
|
||||
///
|
||||
alias TextureRectAllocator = TextureAllocator!GL_TEXTURE_RECTANGLE;
|
||||
///
|
||||
alias TextureRectOverwriter = TextureOverwriter!GL_TEXTURE_RECTANGLE;
|
||||
|
||||
/// RefCounted version of Texture.
|
||||
template TextureRef(GLenum target) {
|
||||
alias TextureRef = RefCounted!(Texture!target);
|
||||
}
|
||||
|
||||
/// A wrapper type for OpenGL texture.
|
||||
///
|
||||
/// Usually this is wrapped by RefCounted.
|
||||
/// When it's in default, empty() property returns true and id() property is invalid.
|
||||
struct Texture(GLenum target_) {
|
||||
mixin GLObject!(
|
||||
(x, y) => gl.GenTextures(x, y),
|
||||
(x) => gl.BindTexture(target_, x),
|
||||
(x) => gl.DeleteTextures(1, x)
|
||||
);
|
||||
public:
|
||||
///
|
||||
enum target = target_;
|
||||
|
||||
/// Binds this texture to the texture unit.
|
||||
///
|
||||
/// This texture will be bound.
|
||||
void BindToUnit(GLenum unit) {
|
||||
assert(!empty);
|
||||
gl.ActiveTexture(unit);
|
||||
Bind();
|
||||
}
|
||||
|
||||
/// Generates mipmaps of this texture.
|
||||
///
|
||||
/// This texture must be bound.
|
||||
void GenerateMipmap() {
|
||||
assert(!empty);
|
||||
gl.GenerateMipmap(target_);
|
||||
}
|
||||
}
|
||||
|
||||
/// An allocator for 2D textures.
|
||||
struct TextureAllocator(GLenum target)
|
||||
if (target.IsSupported2DTextureTarget()) {
|
||||
public:
|
||||
///
|
||||
int level;
|
||||
///
|
||||
GLint internalFormat;
|
||||
///
|
||||
vec2i size;
|
||||
///
|
||||
GLint border;
|
||||
///
|
||||
GLenum format;
|
||||
///
|
||||
GLenum type;
|
||||
///
|
||||
const(void)* data;
|
||||
|
||||
/// Allocates the texture with parameters this has.
|
||||
///
|
||||
/// The texture will be bound.
|
||||
void Allocate(ref TextureRef!target texture)
|
||||
in {
|
||||
assert(!texture.empty);
|
||||
|
||||
assert(level >= 0);
|
||||
assert(size.x > 0 && size.y > 0);
|
||||
assert(border == 0);
|
||||
}
|
||||
do {
|
||||
texture.Bind();
|
||||
gl.TexImage2D(target, level.to!GLint, internalFormat,
|
||||
size.x.to!GLsizei, size.y.to!GLsizei, border, format, type, data);
|
||||
}
|
||||
}
|
||||
|
||||
/// An overwriter for 2D textures.
|
||||
struct TextureOverwriter(GLenum target)
|
||||
if (target.IsSupported2DTextureTarget()) {
|
||||
public:
|
||||
///
|
||||
int level;
|
||||
///
|
||||
vec2i offset;
|
||||
///
|
||||
vec2i size;
|
||||
///
|
||||
GLenum format;
|
||||
///
|
||||
GLenum type;
|
||||
///
|
||||
const(void)* data;
|
||||
|
||||
/// Overwrites the texture with parameters this has.
|
||||
///
|
||||
/// The texture will be bound.
|
||||
void Overwrite(ref TextureRef!target texture)
|
||||
in {
|
||||
assert(!texture.empty);
|
||||
|
||||
assert(level >= 0);
|
||||
assert(offset.x >= 0 && offset.y >= 0);
|
||||
assert(size.x > 0 && size.y > 0);
|
||||
|
||||
}
|
||||
do {
|
||||
texture.Bind();
|
||||
gl.TexImage2D(target, level.to!GLint,
|
||||
offset.x.to!GLint, offset.y.to!GLint,
|
||||
size.x.to!GLsizei, size.y.to!GLsizei,
|
||||
format, type, data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns: whether the target is supported 2d texture
|
||||
@property bool IsSupported2DTextureTarget(GLenum target) {
|
||||
return
|
||||
target == GL_TEXTURE_2D ||
|
||||
target == GL_TEXTURE_RECTANGLE;
|
||||
}
|
84
thirdparty/gl4d/src/gl4d/VertexArray.d
vendored
Normal file
84
thirdparty/gl4d/src/gl4d/VertexArray.d
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
/// License: MIT
|
||||
module gl4d.VertexArray;
|
||||
|
||||
import std.conv,
|
||||
std.typecons;
|
||||
|
||||
import gl4d.gl,
|
||||
gl4d.math,
|
||||
gl4d.Buffer,
|
||||
gl4d.GLObject;
|
||||
|
||||
/// RefCounted version of VertexArray.
|
||||
alias VertexArrayRef = RefCounted!VertexArray;
|
||||
|
||||
/// A wrapper type for OpenGL vertex array.
|
||||
///
|
||||
/// Usually this is wrapped by RefCounted.
|
||||
/// When it's in default, empty() property returns true and id() property is invalid.
|
||||
struct VertexArray {
|
||||
mixin GLObject!(
|
||||
(x, y) => gl.GenVertexArrays(x, y),
|
||||
(x) => gl.BindVertexArray(x),
|
||||
(x) => gl.DeleteVertexArrays(1, x)
|
||||
);
|
||||
|
||||
public:
|
||||
~this() {
|
||||
// Forces unrefering all buffers.
|
||||
foreach (key; attachments_.keys) {
|
||||
attachments_[key] = ArrayBufferRef.init;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ArrayBufferRef[int] attachments_;
|
||||
}
|
||||
|
||||
/// An attacher between array buffers and vertex arrays.
|
||||
struct VertexArrayAttacher {
|
||||
public:
|
||||
///
|
||||
int index;
|
||||
///
|
||||
GLenum type;
|
||||
///
|
||||
int dimension;
|
||||
///
|
||||
bool normalized;
|
||||
///
|
||||
int stride;
|
||||
///
|
||||
int offset;
|
||||
///
|
||||
int divisor;
|
||||
|
||||
/// Attaches the buffer to the vertex array with parameters this has.
|
||||
/// (The vertex array must be bound.)
|
||||
void Attach(ref VertexArrayRef va, ref ArrayBufferRef buf)
|
||||
in {
|
||||
assert(!va.empty);
|
||||
assert(!buf.empty);
|
||||
|
||||
assert(index >= 0);
|
||||
assert(0 < dimension && dimension <= 4);
|
||||
assert(stride >= 0);
|
||||
assert(offset >= 0);
|
||||
assert(divisor >= 0);
|
||||
}
|
||||
do {
|
||||
va.attachments_[index] = buf;
|
||||
|
||||
buf.Bind();
|
||||
|
||||
const i = index.to!GLuint;
|
||||
gl.EnableVertexAttribArray(i);
|
||||
|
||||
gl.VertexAttribPointer(
|
||||
i, dimension.to!GLint,
|
||||
type, normalized, stride.to!GLsizei,
|
||||
cast(GLvoid*) offset.to!ptrdiff_t);
|
||||
|
||||
gl.VertexAttribDivisor(i, divisor.to!GLuint);
|
||||
}
|
||||
}
|
65
thirdparty/gl4d/src/gl4d/gl.d
vendored
Normal file
65
thirdparty/gl4d/src/gl4d/gl.d
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
/// License: MIT
|
||||
module gl4d.gl;
|
||||
|
||||
import std.conv,
|
||||
std.exception,
|
||||
std.format;
|
||||
|
||||
public import bindbc.opengl;
|
||||
|
||||
/// This class is just for separating gl functions from the global namespace.
|
||||
abstract class gl {
|
||||
public:
|
||||
/// This library requires this version.
|
||||
enum RequiredVersion = GLSupport.gl33;
|
||||
|
||||
/// Applies current OpenGL context for gl4d features.
|
||||
///
|
||||
/// If the context version is not equal to RequiredVersion,
|
||||
/// an exception will be thrown.
|
||||
static void ApplyContext() {
|
||||
const loaded = loadOpenGL();
|
||||
(RequiredVersion == loaded).
|
||||
enforce("Loading OpenGL failed with GLSupport %s. (expected %s)".
|
||||
format(loaded, RequiredVersion));
|
||||
}
|
||||
|
||||
/// Calls OpenGL function with error handling.
|
||||
static auto opDispatch(string func,
|
||||
string file = __FILE__, size_t line = __LINE__, Args...)(Args args) {
|
||||
scope (exit) {
|
||||
auto err = glGetError();
|
||||
(err == GL_NO_ERROR).
|
||||
enforce(GetErrorString(err), file, line);
|
||||
}
|
||||
return mixin("gl"~func~"(args)");
|
||||
}
|
||||
|
||||
private:
|
||||
static string GetErrorString(GLenum err) {
|
||||
switch (err) {
|
||||
case GL_NO_ERROR:
|
||||
return "GL_NO_ERROR "~
|
||||
"(No error has been recorded."~
|
||||
" The value of this symbolic constant is guaranteed to be 0.)";
|
||||
case GL_INVALID_ENUM:
|
||||
return "GL_INVALID_ENUM "~
|
||||
"(An unacceptable value is specified for an enumerated argument."~
|
||||
" The offending command is ignored and has no other side effect than to set the error flag.)";
|
||||
case GL_INVALID_VALUE:
|
||||
return "GL_INVALID_VALUE "~
|
||||
"(A numeric argument is out of range. "~
|
||||
"The offending command is ignored and has no other side effect than to set the error flag.)";
|
||||
case GL_INVALID_OPERATION:
|
||||
return "GL_INVALID_OPERATION "~
|
||||
"(The specified operation is not allowed in the current state."~
|
||||
" The offending command is ignored and has no other side effect than to set the error flag.)";
|
||||
case GL_OUT_OF_MEMORY:
|
||||
return "GL_OUT_OF_MEMORY "~
|
||||
"(There is not enough memory left to execute the command."~
|
||||
" The state of the GL is undefined, except for the state of the error flags, after this error is recorded.";
|
||||
default:
|
||||
return err.to!string;
|
||||
}
|
||||
}
|
||||
}
|
6
thirdparty/gl4d/src/gl4d/math.d
vendored
Normal file
6
thirdparty/gl4d/src/gl4d/math.d
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/// License: MIT
|
||||
module gl4d.math;
|
||||
|
||||
public {
|
||||
import gl3n.linalg;
|
||||
}
|
17
thirdparty/gl4d/src/gl4d/package.d
vendored
Normal file
17
thirdparty/gl4d/src/gl4d/package.d
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/// License: MIT
|
||||
module gl4d;
|
||||
|
||||
public {
|
||||
import gl4d.gl,
|
||||
gl4d.math,
|
||||
gl4d.util;
|
||||
|
||||
import gl4d.Buffer,
|
||||
gl4d.Framebuffer,
|
||||
gl4d.Program,
|
||||
gl4d.Renderbuffer,
|
||||
gl4d.Sampler,
|
||||
gl4d.Shader,
|
||||
gl4d.Texture,
|
||||
gl4d.VertexArray;
|
||||
}
|
50
thirdparty/gl4d/src/gl4d/util/ModelMatrixFactory.d
vendored
Normal file
50
thirdparty/gl4d/src/gl4d/util/ModelMatrixFactory.d
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
/// License: MIT
|
||||
module gl4d.util.ModelMatrixFactory;
|
||||
|
||||
import gl3n.linalg;
|
||||
|
||||
///
|
||||
struct ModelMatrixFactory(size_t dim) if (dim == 3 || dim == 4) {
|
||||
public:
|
||||
///
|
||||
alias mat = Matrix!(float, dim, dim);
|
||||
|
||||
///
|
||||
mat Create() const {
|
||||
auto m = mat.identity;
|
||||
static if (dim == 3) {
|
||||
m.scale(scale.x, scale.y, 1);
|
||||
m.rotatex(rotation.x);
|
||||
m.rotatey(rotation.y);
|
||||
m.rotatez(rotation.z);
|
||||
m.translate(translation.x, translation.y, 1);
|
||||
|
||||
} else static if (dim == 4) {
|
||||
m.scale(scale.x, scale.y, scale.z);
|
||||
m.rotatex(rotation.x);
|
||||
m.rotatey(rotation.y);
|
||||
m.rotatez(rotation.z);
|
||||
m.translate(translation.x, translation.y, translation.z);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
static if (dim == 3) {
|
||||
///
|
||||
vec2 scale = vec2(1, 1);
|
||||
///
|
||||
vec3 rotation = vec3(0, 0, 0);
|
||||
///
|
||||
vec2 translation = vec2(0, 0);
|
||||
|
||||
} else static if (dim == 4) {
|
||||
///
|
||||
vec3 scale = vec3(1, 1, 1);
|
||||
///
|
||||
vec3 rotation = vec3(0, 0, 0);
|
||||
///
|
||||
vec3 translation = vec3(0, 0, 0);
|
||||
}
|
||||
}
|
||||
static assert(__traits(compiles, ModelMatrixFactory!3));
|
||||
static assert(__traits(compiles, ModelMatrixFactory!4));
|
22
thirdparty/gl4d/src/gl4d/util/ProjectionMatrixFactory.d
vendored
Normal file
22
thirdparty/gl4d/src/gl4d/util/ProjectionMatrixFactory.d
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/// License: MIT
|
||||
module gl4d.util.ProjectionMatrixFactory;
|
||||
|
||||
import gl3n.linalg;
|
||||
|
||||
///
|
||||
struct ProjectionMatrixFactory {
|
||||
public:
|
||||
///
|
||||
mat4 Create() const {
|
||||
return mat4.perspective(aspect, 1, fov, near, far);
|
||||
}
|
||||
|
||||
///
|
||||
float aspect = 1;
|
||||
///
|
||||
float fov = 60;
|
||||
///
|
||||
float far = 100;
|
||||
///
|
||||
float near = 0.1;
|
||||
}
|
20
thirdparty/gl4d/src/gl4d/util/ViewMatrixFactory.d
vendored
Normal file
20
thirdparty/gl4d/src/gl4d/util/ViewMatrixFactory.d
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
/// License: MIT
|
||||
module gl4d.util.ViewMatrixFactory;
|
||||
|
||||
import gl3n.linalg;
|
||||
|
||||
///
|
||||
struct ViewMatrixFactory {
|
||||
public:
|
||||
///
|
||||
mat4 Create() const {
|
||||
return mat4.look_at(pos, target, up);
|
||||
}
|
||||
|
||||
///
|
||||
vec3 pos = vec3(0, -1, 0);
|
||||
///
|
||||
vec3 target = vec3(0, 0, 0);
|
||||
///
|
||||
vec3 up = vec3(0, 1, 0);
|
||||
}
|
8
thirdparty/gl4d/src/gl4d/util/package.d
vendored
Normal file
8
thirdparty/gl4d/src/gl4d/util/package.d
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/// License: MIT
|
||||
module gl4d.util;
|
||||
|
||||
public {
|
||||
import gl4d.util.ModelMatrixFactory,
|
||||
gl4d.util.ProjectionMatrixFactory,
|
||||
gl4d.util.ViewMatrixFactory;
|
||||
}
|
201
thirdparty/gl4d/test.d
vendored
Normal file
201
thirdparty/gl4d/test.d
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env dub
|
||||
|
||||
/+ dub.json:
|
||||
{
|
||||
"name": "test",
|
||||
|
||||
"dependencies": {
|
||||
"bindbc-sdl": "~>0.11.0",
|
||||
"gl4d": {"path": "."}
|
||||
},
|
||||
"versions": ["SDL_209"]
|
||||
}
|
||||
+/
|
||||
|
||||
import std;
|
||||
import bindbc.sdl;
|
||||
import gl4d;
|
||||
|
||||
enum ShaderHeader = "#version 330 core
|
||||
#extension GL_ARB_explicit_uniform_location : enable";
|
||||
|
||||
enum VertexShaderSource = ShaderHeader~q{
|
||||
layout(location=0) in vec4 pos;
|
||||
|
||||
layout(std140) uniform uniformblock {
|
||||
float value;
|
||||
} ub;
|
||||
|
||||
out vec2 uv;
|
||||
out float feedback_value;
|
||||
|
||||
void main() {
|
||||
uv = pos.xy;
|
||||
gl_Position = pos * ub.value;
|
||||
feedback_value = length(pos.xy);
|
||||
}
|
||||
};
|
||||
enum FragmentShaderSource = ShaderHeader~q{
|
||||
in vec2 uv;
|
||||
|
||||
layout(std140) uniform uniformblock {
|
||||
float value;
|
||||
} ub;
|
||||
|
||||
layout(location = 1) uniform sampler2D tex;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
void main() {
|
||||
color = texture(tex, uv);
|
||||
}
|
||||
};
|
||||
|
||||
void Test() {
|
||||
auto tex = Texture2D.Create();
|
||||
{
|
||||
auto data = new ubyte[16*16];
|
||||
data[] = ubyte.max;
|
||||
|
||||
Texture2DAllocator allocator;
|
||||
allocator.internalFormat = GL_RGBA8;
|
||||
allocator.size = vec2i(16, 16);
|
||||
allocator.format = GL_RED;
|
||||
allocator.type = GL_UNSIGNED_BYTE;
|
||||
allocator.data = data.ptr;
|
||||
allocator.Allocate(tex);
|
||||
}
|
||||
|
||||
auto sampler = Sampler.Create();
|
||||
{
|
||||
SamplerConfigurer configurer;
|
||||
configurer.filterMin = GL_NEAREST;
|
||||
configurer.filterMag = GL_NEAREST;
|
||||
configurer.Configure(sampler);
|
||||
}
|
||||
|
||||
auto buf = ArrayBuffer.Create();
|
||||
{
|
||||
ArrayBufferAllocator allocator;
|
||||
allocator.size = float.sizeof*4*3;
|
||||
allocator.data = null;
|
||||
allocator.usage = GL_STATIC_DRAW;
|
||||
allocator.Allocate(buf);
|
||||
|
||||
auto ptr = buf.MapToWrite!float();
|
||||
ptr[0] = 0;
|
||||
ptr[1] = 0;
|
||||
ptr[2] = 0;
|
||||
ptr[3] = 1;
|
||||
|
||||
ptr[4] = 0.5;
|
||||
ptr[5] = 0.5;
|
||||
ptr[6] = 0;
|
||||
ptr[7] = 1;
|
||||
|
||||
ptr[8] = 0.5;
|
||||
ptr[9] = 0;
|
||||
ptr[10] = 0;
|
||||
ptr[11] = 1;
|
||||
}
|
||||
|
||||
auto buf_tf = ArrayBuffer.Create();
|
||||
{
|
||||
ArrayBufferAllocator allocator;
|
||||
allocator.size = float.sizeof * 3;
|
||||
allocator.data = null;
|
||||
allocator.usage = GL_STREAM_COPY;
|
||||
allocator.Allocate(buf_tf);
|
||||
}
|
||||
|
||||
auto ub = UniformBuffer.Create();
|
||||
{
|
||||
const data = 1.5f;
|
||||
UniformBufferAllocator allocator;
|
||||
allocator.size = float.sizeof;
|
||||
allocator.data = &data;
|
||||
allocator.usage = GL_STATIC_DRAW;
|
||||
allocator.Allocate(ub);
|
||||
}
|
||||
|
||||
ProgramRef program;
|
||||
{
|
||||
ProgramLinker linker;
|
||||
linker.vertex = VertexShader.Compile(VertexShaderSource);
|
||||
linker.fragment = FragmentShader.Compile(FragmentShaderSource);
|
||||
linker.feedbackVaryings = ["feedback_value"];
|
||||
linker.feedbackInterleaved = true;
|
||||
program = linker.Link();
|
||||
program.NumberUniformBlocks!(["uniformblock"]);
|
||||
|
||||
program.Use();
|
||||
program.uniform!1 = 0;
|
||||
}
|
||||
|
||||
auto va = VertexArray.Create();
|
||||
{
|
||||
va.Bind();
|
||||
VertexArrayAttacher attacher;
|
||||
attacher.index = 0;
|
||||
attacher.type = GL_FLOAT;
|
||||
attacher.dimension = 4;
|
||||
attacher.Attach(va, buf);
|
||||
}
|
||||
|
||||
auto rb = Renderbuffer.Create();
|
||||
{
|
||||
RenderbufferAllocator allocator;
|
||||
allocator.format = GL_RGB8;
|
||||
allocator.size = vec2i(32, 32);
|
||||
allocator.Allocate(rb);
|
||||
}
|
||||
|
||||
auto fb = Framebuffer.Create();
|
||||
{
|
||||
fb.Bind();
|
||||
fb.attachment!GL_COLOR_ATTACHMENT0 = rb;
|
||||
fb.attachmentOrder = [GL_COLOR_ATTACHMENT0];
|
||||
fb.Validate();
|
||||
fb.Unbind();
|
||||
}
|
||||
|
||||
buf_tf.BindForTransformFeedback(0);
|
||||
ub.BindForUniformBlock(0);
|
||||
tex.BindToUnit(GL_TEXTURE0);
|
||||
sampler.Bind(0);
|
||||
|
||||
gl.BeginTransformFeedback(GL_POINTS);
|
||||
gl.PointSize(5);
|
||||
gl.DrawArrays(GL_POINTS, 0, 3);
|
||||
gl.EndTransformFeedback();
|
||||
|
||||
{
|
||||
auto ptr = buf_tf.MapToRead!float();
|
||||
static foreach (i; 0..3) ptr[i].writeln;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
(loadSDL() == sdlSupport).
|
||||
enforce("SDL library loading failed.");
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
scope(exit) SDL_Quit();
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
|
||||
auto win = SDL_CreateWindow("gl4d testing", 0, 0, 100, 100, SDL_WINDOW_OPENGL).
|
||||
enforce("Failed creating OpenGL window.");
|
||||
scope(exit) SDL_DestroyWindow(win);
|
||||
|
||||
auto context = SDL_GL_CreateContext(win);
|
||||
SDL_GL_MakeCurrent(win, context);
|
||||
gl.ApplyContext();
|
||||
|
||||
Test();
|
||||
|
||||
SDL_GL_SwapWindow(win);
|
||||
SDL_Delay(3000);
|
||||
}
|
Reference in New Issue
Block a user