Compare commits

..

12 Commits

24 changed files with 3050 additions and 59 deletions

1
.vim/ftdetect/kyn.vim Normal file
View File

@@ -0,0 +1 @@
autocmd BufNewFile,BufRead *.kyn setfiletype kyn

52
.vim/indent/kyn.vim Normal file
View File

@@ -0,0 +1,52 @@
" Vim indent file
" Language: Kyn
" Maintainer: Gemini
if exists("b:did_indent")
finish
endif
let b:did_indent = 1
setlocal indentexpr=GetKynIndent()
setlocal indentkeys+=={,},0)
let b:undo_indent = "setlocal indentexpr< indentkeys<"
" Only define the function once
if exists("*GetKynIndent")
finish
endif
function GetKynIndent()
" Get the line number of the current line
let lnum = v:lnum
" Get the current line
let cline = getline(lnum)
" If the current line has a '}', decrease indent
if cline =~ '^\s*}'
let lnum = prevnonblank(lnum - 1)
return indent(lnum)
endif
" Find the previous non-blank line
let lnum = prevnonblank(lnum - 1)
" At the start of the file, no indent
if lnum == 0
return 0
endif
" Get the indent of the previous line
let prev_indent = indent(lnum)
let prev_line = getline(lnum)
" If the previous line ends with '{', increase indent
if prev_line =~ '{\s*$'
return prev_indent + &shiftwidth
endif
" Otherwise, keep the same indent
return prev_indent
endfunction

47
.vim/syntax/kyn.vim Normal file
View File

@@ -0,0 +1,47 @@
" Vim syntax file
" Language: Kyn
if exists("b:current_syntax")
finish
endif
" Keywords
syn keyword kynKeyword fun struct let if while return assert is
" Instructions/Modules
syn keyword kynInstruction println print math exit compare input concat split file
" Comments
syn match kynComment /#.*/
" Strings
syn region kynString start=/"/ end=/"/
" Numbers
syn match kynNumber /\d\+\(\.\d\+\)\?/
" Variables
syn match kynVariable /\$[a-zA-Z_][a-zA-Z0-9_]*/
" Type Placeholders
syn match kynType /<[a-zA-Z_][a-zA-Z0-9_]*>/
" Operators
syn match kynOperator /[=+\-*\/^%]/
syn match kynOperator /==\|!=\|>=\|<=/
" Special variables
syn keyword kynSpecial self
" Highlighting links
hi def link kynKeyword Keyword
hi def link kynInstruction Statement
hi def link kynComment Comment
hi def link kynString String
hi def link kynNumber Number
hi def link kynVariable Identifier
hi def link kynType Type
hi def link kynOperator Operator
hi def link kynSpecial Special
let b:current_syntax = "kyn"

View File

@@ -27,8 +27,9 @@ TARGET = kyn
all: $(TARGET)
# Link the object files to create the executable
# At this point, we do -lcurl to link curl for the request lib
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS)
$(CXX) $(CXXFLAGS) -lcurl -o $(TARGET) $(OBJS)
# Compile .cpp files to .o files in the build directory
$(BUILD_DIR)/%.o: %.cpp

View File

@@ -39,7 +39,7 @@ Value handleListGet(const Value& subject, const std::vector<Value>& args) {
return Value();
}
Value accessor = args[0];
if (accessor.valtype == ValueType::String && accessor.string_val == "size") {
if (accessor.valtype == ValueType::Identifier && accessor.string_val == "size") {
return Value((long long)subject.list.size());
}
if (accessor.valtype != ValueType::Int) {

View File

@@ -39,7 +39,7 @@ Value handleStringGet(const Value& subject, const std::vector<Value>& args) {
}
Value accessor = args[0];
if (accessor.valtype != ValueType::Int) {
if (accessor.valtype == ValueType::String && accessor.string_val == "size") {
if (accessor.valtype == ValueType::Identifier && accessor.string_val == "size") {
return Value((long long)subject.string_val.length());
}
error("String accessor must be an integer or \"size\"");
@@ -51,7 +51,7 @@ Value handleStringGet(const Value& subject, const std::vector<Value>& args) {
error("String index out of bounds");
return Value();
}
return Value(std::string(1, subject.string_val.at(index)));
return Value("\"" + std::string(1, subject.string_val.at(index)) + "\"");
}
void handleStringSet(std::string var_name, Value& subject, const Value& index_val, const Value& new_val) {

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <utility>
#include "../datatypes/lists/lists.h"
#include "../utils/escapes/escapes.h"
InstructionType strToInstructionType(std::string in) {
if (in == "println") return InstructionType::Println;
@@ -22,6 +23,7 @@ InstructionType strToInstructionType(std::string in) {
else if (in == "file") return InstructionType::File;
else if (in == "struct") return InstructionType::Struct;
else if (in == "assert") return InstructionType::Assert;
else if (in == "request") return InstructionType::Request;
else return InstructionType::Variable;
}
@@ -52,7 +54,11 @@ Instruction::Instruction(std::vector<Value> toks) {
} else if (toks[0].valtype == ValueType::Variable) {
instruction = InstructionType::Variable;
} else {
error("Instruction should be an identifier or variable value");
if (inReplMode) {
instruction = InstructionType::Variable;
} else {
error("Instruction should be an identifier or variable value");
}
}
if (instruction == InstructionType::Variable) {
for (const auto& tok : toks) {
@@ -71,20 +77,20 @@ Instruction::Instruction(std::vector<Value> toks) {
Value::Value(std::string stringval) {
// This constructor will attempt to parse the string into the most specific type possible.
if (stringval.length() >= 2 && stringval.front() == '"' && stringval.back() == '"') {
valtype = ValueType::String;
string_val = interpretEscapeSequences(stringval.substr(1, stringval.length() - 2));
return;
}
if (stringval.length() > 2 && stringval.front() == '<' && stringval.back() == '>') {
valtype = ValueType::TypePlaceholder;
type_placeholder_name = stringval.substr(1, stringval.length() - 2);
return;
}
if (stringval.length() >= 2 && stringval.front() == '"' && stringval.back() == '"') {
valtype = ValueType::String;
string_val = stringval.substr(1, stringval.length() - 2);
return;
}
if (stringval.empty()) {
valtype = ValueType::String;
valtype = ValueType::None;
string_val = "";
return;
}
@@ -268,7 +274,7 @@ std::vector<Value> split(std::string line) {
splitvals.push_back(Value(buf));
}
buf = "";
} else if (chr == '(' || chr == '[') {
} else if ((chr == '(' || chr == '[') && !instring) {
if (brackets == 0) {
bracket_type = chr;
if (!buf.empty()) {
@@ -279,7 +285,7 @@ std::vector<Value> split(std::string line) {
buf += chr;
}
brackets++;
} else if (chr == ')' || chr == ']') {
} else if ((chr == ')' || chr == ']') && !instring) {
brackets--;
if (brackets == 0) {
if (!buf.empty()) {

View File

@@ -6,11 +6,11 @@
#include <map>
enum class InstructionType {
None, Print, Println, Math, Let, Variable, Exit, If, While, Input, Compare, Function, Struct, Return, Concat, Split, File, Assert
None, Print, Println, Math, Let, Variable, Exit, If, While, Input, Compare, Function, Struct, Return, Concat, Split, File, Assert, Request
};
enum class ValueType {
Processed, Variable, InstructionGroup, List, Map, String, Int, Double, Identifier, Custom, TypePlaceholder
Processed, Variable, InstructionGroup, List, Map, String, Int, Double, Identifier, Custom, TypePlaceholder, None
};
struct Instruction;
@@ -67,6 +67,7 @@ struct Instruction {
InstructionType instruction = InstructionType::None;
std::vector<Value> args;
std::string toString() const;
int lineNum;
Instruction() = default;
Instruction(std::vector<std::string> toks);
Instruction(std::vector<Value> toks);

View File

@@ -18,6 +18,7 @@
#include "../modules/split/split.h"
#include "../modules/concat/concat.h"
#include "../modules/file/file.h"
#include "../modules/request/request.h"
#include "../modules/assert/assert.h"
// Forward declaration for mutual recursion
@@ -43,6 +44,8 @@ Value execute(Instruction inst) {
if (inst.instruction == InstructionType::Assert) {
return modules::assert(inst.args);
}
// Handle variable operations that require unevaluated arguments
if (inst.instruction == InstructionType::Variable) {
auto& args = inst.args;
@@ -116,8 +119,6 @@ Value execute(Instruction inst) {
return instance;
}
// Indexed assignment: $var index = value
if (args.size() == 4 && args[2].valtype == ValueType::Identifier && args[2].string_val == "=") {
std::string var_name;
@@ -165,14 +166,23 @@ Value execute(Instruction inst) {
data::modifyValue(var_name, new_val);
return Value();
}
}
// Variable access or indexed get
// If args[0] is an Identifier and there are no args, it's a variable access.
if (args[0].valtype == ValueType::Identifier && args.empty()) {
return data::getValue(args[0].string_val);
// For all other instructions, evaluate the arguments first
for(auto& arg : inst.args) {
arg = evaluate(arg);
}
if (inst.instruction == InstructionType::Variable) {
// This part handles variable access and member/method access on evaluated arguments
auto& args = inst.args;
if (args.empty()) {
error("Empty variable instruction after evaluation");
return Value();
}
Value subject = evaluate(args[0]);
Value subject = args[0]; // First arg is already evaluated
std::vector<Value> op_args(args.begin() + 1, args.end());
if (op_args.empty()) {
@@ -182,14 +192,14 @@ Value execute(Instruction inst) {
// Indexed get: subject index
if (subject.valtype == ValueType::List) {
return handleListGet(subject, op_args);
} else if (subject.valtype == ValueType::String) {
} else if (subject.valtype == ValueType::String) {
return handleStringGet(subject, op_args);
} else if (subject.valtype == ValueType::Map) {
if (op_args.empty()) {
return subject;
}
if (op_args[0].valtype != ValueType::Identifier) {
error("Struct member name must be an identifier.");
if (op_args[0].valtype != ValueType::Identifier && op_args[0].valtype != ValueType::String) {
error("Struct member name must be an identifier or string.");
return Value();
}
std::string member_name = op_args[0].string_val;
@@ -211,7 +221,7 @@ Value execute(Instruction inst) {
data::pushScope();
data::scopes.back()["self"] = subject;
for (size_t i = 0; i < func.arg_names.size(); ++i) {
data::scopes.back()[func.arg_names[i]] = evaluate(func_args[i]);
data::scopes.back()[func.arg_names[i]] = func_args[i];
}
Value return_val;
for (const auto& body_inst : func.body) {
@@ -234,11 +244,6 @@ Value execute(Instruction inst) {
return Value();
}
// For all other instructions, evaluate the arguments first
for(auto& arg : inst.args) {
arg = evaluate(arg);
}
// Then, execute the instruction with the evaluated arguments
switch (inst.instruction) {
case InstructionType::Print:
@@ -257,6 +262,8 @@ Value execute(Instruction inst) {
return modules::concat(inst.args);
case InstructionType::File:
return modules::file(inst.args);
case InstructionType::Request:
return modules::request(inst.args);
case InstructionType::Exit:
return modules::exit(inst.args);
case InstructionType::Return: {

View File

@@ -7,6 +7,7 @@ Value modules::concat(std::vector<Value> args) {
std::string buf;
for (Value val : args) {
switch (val.valtype) {
case ValueType::Identifier:
case ValueType::String:
buf += val.string_val;
break;
@@ -17,7 +18,7 @@ Value modules::concat(std::vector<Value> args) {
buf += std::to_string(val.double_val);
break;
default:
error("Can only concatenate String, Int, and Double values");
error("Can only concatenate String, Int, and Double values. Value given: " + val.toString());
break;
}
}

View File

@@ -22,17 +22,12 @@ Value modules::file(std::vector<Value> args) {
}
std::string buf;
std::string retval;
bool notfirst = false;
while (getline(file, buf)) {
if (notfirst) {
buf += "\n";
} else {
notfirst = true;
}
buf += "\n";
retval += buf;
}
file.close();
return Value(retval);
return Value("\"" + retval + "\"");
} else if (args[0].string_val == "readlines") {
std::ifstream file(args[1].string_val);
if (!file) {
@@ -42,7 +37,7 @@ Value modules::file(std::vector<Value> args) {
std::string buf;
std::vector<Value> retval;
while (getline(file, buf)) {
retval.push_back(Value(buf));
retval.push_back(Value("\"" + buf + "\""));
}
file.close();
return Value(retval);

View File

@@ -0,0 +1,116 @@
#include "request.h"
#include "../../defs/defs.h"
#include "../../error/error.h"
#include <vector>
#include <string>
#include <curl/curl.h>
enum class RequestType {
GET, POST
};
size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) {
size_t totalSize = size * nmemb;
userp->append((char*)contents, totalSize);
return totalSize;
}
std::string curlWrapper(std::string url, RequestType requestType, std::vector<std::string> headers = {"Content-Type: text/plain"}, std::string data = "") {
CURL* curl = curl_easy_init();
std::string response;
struct curl_slist *list = NULL;
if (curl) {
for (const std::string& header : headers) {
list = curl_slist_append(list, header.c_str());
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "kyn-libcurl-agent/1.0");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
if (requestType == RequestType::POST) {
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
}
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
error("Request failed: " + std::string(curl_easy_strerror(res)));
response = "KYNERR";
}
curl_easy_cleanup(curl);
}
return response;
}
Value modules::request(std::vector<Value> args) {
if (args.size() < 2) {
error("Not enough args for request module");
return Value("Error");
}
if (args[1].valtype != ValueType::String) {
error("Expecting a string to identify request URL");
return Value("Error");
}
if (args[0].valtype == ValueType::Identifier) {
if (args[0].string_val == "get") {
if (args.size() == 3) {
if (args[2].valtype == ValueType::List) {
std::vector<std::string> headers;
for (const Value& value : args[2].list) {
if (value.valtype == ValueType::String) {
headers.push_back(value.string_val);
} else {
error("Expecting second argument of submodule get to be a list of strings");
return Value("Error");
}
}
std::string response = curlWrapper(args[1].string_val, RequestType::GET, headers);
if (response == "KYNERR") return Value("Error");
return Value(response);
} else {
error("Expecting second argument of submodule get to be a list of strings");
return Value("Error");
}
} else {
std::string response = curlWrapper(args[1].string_val, RequestType::GET);
if (response == "KYNERR") return Value("Error");
return Value(response);
}
} else if (args[0].string_val == "post") {
if (args.size() < 3 || args[2].valtype != ValueType::String) {
error("post submodule of request expects a string as 2nd arg");
return Value("Error");
}
if (args.size() == 4) {
if (args[3].valtype == ValueType::List) {
std::vector<std::string> headers;
for (const Value& value : args[3].list) {
if (value.valtype == ValueType::String) {
headers.push_back(value.string_val);
} else {
error("Expecting third argument of submodule post to be a list of strings");
return Value("Error");
}
}
std::string response = curlWrapper(args[1].string_val, RequestType::POST, headers, args[2].string_val);
if (response == "KYNERR") return Value("Error");
return Value(response);
} else {
error("Expecting third argument of submodule post to be a list of strings");
return Value("Error");
}
} else {
std::string response = curlWrapper(args[1].string_val, RequestType::POST, {"Content-Type: text/plain"}, args[2].string_val);
if (response == "KYNERR") return Value("Error");
return Value(response);
}
}
}
error("Unknown submodule for request. Only 'get' and 'post' are available.");
return Value("Error");
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include <vector>
#include "../../defs/defs.h"
namespace modules {
Value request(std::vector<Value> args);
}

View File

@@ -1,8 +1,45 @@
#include "split.h"
#include "../../defs/defs.h"
#include "../../error/error.h"
#include <vector>
#include <string>
Value modules::split(std::vector<Value> args) {
return Value("Work in progress!");
if (args.size() < 1) {
error("Not enough args for split module");
}
if (args[0].valtype != ValueType::String) {
error("First argument of split must be a string");
}
std::string delimiter = " ";
if (args.size() > 1) {
if (args[1].valtype == ValueType::String) {
delimiter = args[1].string_val;
} else {
error("Expected a string delimiter as second argument of split");
}
}
std::string buf;
std::vector<Value> list;
for (const char& chr : args[0].string_val) {
buf += chr;
if (buf.size() >= delimiter.size()) {
std::string checker = buf.substr(buf.size() - delimiter.size());
if (checker == delimiter) {
list.push_back(Value("\"" + buf.substr(0, buf.size() - delimiter.size()) + "\""));
buf.clear();
}
}
}
if (!buf.empty()) {
list.push_back(Value("\"" + buf + "\""));
}
return Value(list);
}

View File

@@ -9,8 +9,10 @@ std::vector<Instruction> parse(std::string program) {
std::vector<std::string> lines;
std::string buf;
int blockTracker = 0;
int lineNum = 0;
for (char chr : program) {
if (chr == '\n') lineNum ++;
if (chr == '{') {
blockTracker++;
} else if (chr == '}') {
@@ -23,7 +25,7 @@ std::vector<Instruction> parse(std::string program) {
if ((chr == '\n' || chr == ';') && blockTracker == 0) {
if (!buf.empty()) {
lines.push_back(buf);
lines.push_back(trim(buf));
buf.clear();
}
} else {
@@ -90,9 +92,18 @@ std::vector<Instruction> parse(std::string program) {
error("Expected '{' for else statement");
continue;
}
size_t else_block_end = remaining_line.find_last_of('}');
if (else_block_end == std::string::npos) {
error("Expected '}' for else statement");
size_t else_block_end = 0;
int brace_level = 0;
for (size_t i = else_block_start; i < remaining_line.length(); ++i) {
if (remaining_line[i] == '{') brace_level++;
else if (remaining_line[i] == '}') brace_level--;
if (brace_level == 0) {
else_block_end = i;
break;
}
}
if (else_block_end == 0) {
error("Mismatched braces in else statement");
continue;
}
std::string else_content = remaining_line.substr(else_block_start + 1, else_block_end - else_block_start - 1);

View File

@@ -5,11 +5,12 @@
#include <iostream>
#include <string>
#include "../data/data.h"
#include "../vendor/linenoise/linenoise.hpp"
void repl() {
data::initScopes();
inReplMode = true;
std::cout << "Kyn REPL v0.0.1" << std::endl;
std::cout << "Kyn REPL v0.0.2" << std::endl;
std::cout << "Type 'exit' to exit" << std::endl;
std::string full_command;
@@ -17,14 +18,11 @@ void repl() {
int brace_level = 0;
while (true) {
if (brace_level == 0) {
std::cout << "kyn> ";
} else {
std::cout << ".. > ";
}
auto prompt = brace_level == 0 ? "kyn> " : ".. > ";
auto quit = linenoise::Readline(prompt, line_buf);
if (!getline(std::cin, line_buf)) {
break; // Handle EOF (Ctrl+D)
if (quit) {
break;
}
for (char c : line_buf) {
@@ -44,9 +42,13 @@ void repl() {
if (full_command.find_first_not_of(" \t\n\r") != std::string::npos) {
std::vector<Instruction> parsed = parse(full_command);
for (Instruction inst : parsed) {
execute(inst);
Value instruction = execute(inst);
if (!(instruction.valtype == ValueType::None)) {
std::cout << instruction.toString() << std::endl;
}
}
}
linenoise::AddHistory(full_command.c_str());
full_command.clear();
brace_level = 0;
}

View File

@@ -0,0 +1,34 @@
#include "escapes.h"
#include <string>
std::string interpretEscapeSequences(std::string input) {
std::string output;
for (size_t i = 0; i < input.length(); ++i) {
if (input[i] == '\\' && i + 1 < input.length()) {
char next = input[i + 1];
switch (next) {
case 'n': output += '\n'; break;
case 't': output += '\t'; break;
case 'r': output += '\r'; break;
case 'b': output += '\b'; break;
case 'f': output += '\f'; break;
case 'a': output += '\a'; break;
case 'v': output += '\v'; break;
case '\\': output += '\\'; break;
case '\'': output += '\''; break;
case '\"': output += '\"'; break;
case '0': output += '\0'; break;
default:
output += '\\';
output += next;
break;
}
++i;
} else {
output += input[i];
}
}
return output;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string interpretEscapeSequences(std::string input);

View File

@@ -2,11 +2,11 @@
// Helper to trim whitespace from the start and end of a string
std::string trim(const std::string& str) {
size_t first = str.find_first_not_of(" \t\n\r");
size_t first = str.find_first_not_of(" \t\n");
if (std::string::npos == first) {
return str;
return "";
}
size_t last = str.find_last_not_of(" \t\n\r");
size_t last = str.find_last_not_of(" \t\n");
return str.substr(first, (last - first + 1));
}

0
src/vendor/linenoise/linenoise.h vendored Normal file
View File

2587
src/vendor/linenoise/linenoise.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
let counter = 0
while compare $counter <= 1000 {
while compare $counter <= 10000 {
println $counter
counter = (math 1 + $counter)
}

33
tests/test.kyn Normal file
View File

@@ -0,0 +1,33 @@
println "thing"
if compare 5 == 10 {
println "uh oh its broken"
} else {
println "its working"
}
struct Dingus {
name = <String>
age = <Int>
fun init name age {
self name = name
self age = age
}
fun tostring {
return (concat name ", " age)
}
}
let myDingus = (Dingus "dingus" 23)
fun myFn param1 param2 andSoOn {
println param1
return param2
}
println (myFn 123 "hi" 3.14)
let favNumbers = [7, 14, 21]
println (file read "README.md")

47
tests/toInt.kyn Normal file
View File

@@ -0,0 +1,47 @@
fun toInt in {
assert $in is <String>
let retint = 0
let counter = 0
let power = ($in size)
let size = ($in size)
while compare $counter < $size {
let char = ($in $counter)
power = (math $power - 1)
if compare $char == "1" {
retint = (math $retint + (math 1 * 10 ^ $power))
}
if compare $char == "2" {
retint = (math $retint + (math 2 * 10 ^ $power))
}
if compare $char == "3" {
retint = (math $retint + (math 3 * 10 ^ $power))
}
if compare $char == "4" {
retint = (math $retint + (math 4 * 10 ^ $power))
}
if compare $char == "5" {
retint = (math $retint + (math 5 * 10 ^ $power))
}
if compare $char == "6" {
retint = (math $retint + (math 6 * 10 ^ $power))
}
if compare $char == "7" {
retint = (math $retint + (math 7 * 10 ^ $power))
}
if compare $char == "8" {
retint = (math $retint + (math 8 * 10 ^ $power))
}
if compare $char == "9" {
retint = (math $retint + (math 9 * 10 ^ $power))
}
if compare $char == "0" {
# whole lotta nothing
}
counter = (math $counter + 1)
}
return $retint
}
let myInt = (toInt "4738927643289")
println $myInt