Skip to content

Instantly share code, notes, and snippets.

@ozkriff
Last active December 18, 2015 21:39
Show Gist options
  • Save ozkriff/5848970 to your computer and use it in GitHub Desktop.
Save ozkriff/5848970 to your computer and use it in GitHub Desktop.
Очень-преочень черновой набросок примера программы на Misery и кое-какая документация.
#
# MISERY LANGUAGE
# ===============
#
# Github
# ------
#
# github.com/ozkriff/misery
#
#
# Форматирование и пробелы
# ------------------------
#
# Отступ - 2 пробела.
#
# Если "(" и ")" помещаются на одной строчке, то пробел вокруг
# них не ставится. Иначе, собственно, ставится.
# 'if', 'else', 'for', '->' - всегда окружены пробелами.
#
#
# Инъекция зависимостей
# ---------------------
#
# Использовать и любить, да.
#
#
# Модульные тесты
# ---------------
#
# Проверять покрытие кода модульными проверками.
# Каждый путь выполнения в модуле должен быть выполнен при запуске
# соответствующего test_*.py файла!
# Покрытие функционаьных проверок не учитывается.
#
#
# Функциональные тесты
# --------------------
#
# Сложные тесты, использующие сразу несколько модулей, надо вынести
# в отдельный блок функциональных тестов.
#
# Пытаться скомпилировать их, слинковать, запустить и проверив вывод.
#
# Возможно, стоит вынести их из исходников в специальные файлы.
#
#
# Функции
# -------
#
# Изначально я хотел использовать синтаксис "funcName := func(){}",
# но это слобо согласуется с той же перегрузкой функций, т.к. нельзя создать
# безымянную перегруженную функцию.
#
# Реализовать перегрузку функций и методов.
#
# Функция может иметь параметры со значением по-умолчанию:
#
# killHim := func (victim String = "Bill") {...}
#
# Методы \ Прикрепленные функции:
#
# Ключевое слово "this" для обращения к полям прикрепленного объекта
# и "this*" для мутирующих методов.
#
# Аналог питонячего yield`а. Почти как return.
#
#
# Импорт
# ------
#
# Импорт как в питоне: "import std.Vector", тогда можно писать
# не "std.Vector", а просто "Vector".
# Хотя, надо по-лучше подумать над "qualified import".
#
# Не разрешать циклические зависимости между модулями.
#
# Возможно, использовать два слова - 'use' и 'import'.
#
#
# Инкапсуляция
# ------------
#
# Если имя чего-то начинается с "_", то оно недоступно
# извне родительской сущности.
#
#
# Иммутабельность
# ---------------
#
# Если последний символ в имени переменной "*" - то это изменяемая переменная.
# иначе - константная переменная (значение задается при конструировании и
# потом не меняется).
#
# "*" в конце имени метода делает его мутирующим, иначе он не сможет
# изменять состояние объекта.
#
# "*" в конце параметра функции делает его изменяемым.
# При передаче изменяемых аргументов должен ставиться некий значек:
#
# inc(+intVar)
#
#
# Switch/case утверждение
# -----------------------
#
# Каноничный switch/case:
#
# switch [optionalExpression] (
# case [expression] (
# # ...
# )
# case [expression] (
# # ...
# fallthrough
# )
# case (
# [expression1]
# [expression2]
# ) (
# # ...
# )
# else (
# # ...
# )
# )
#
# и его сокращенный вариант "if":
#
# if [expression] (
# # ...
# )
#
# и "if\else":
#
# if [expression] (
# # ...
# ) else (
# # ...
# )
#
# Наличествует "fallthrough" statement, позволяющий "провалиться"
# в следующий 'case' блок.
#
#
# Обработка ошибок
# ----------------
#
# Да, подумай на тем, что бы сделать handle блок ближке к try-catch
# конструкции, а не "прицепом" к блокам. Или надо вносить handle секцию
# в синтаксис всех блоков.
#
# Если есть функция, которая возвращает наследника std.Error и вызывающий
# явным образом не проверяет его, то его тип обязан появиться в секции
# 'handle' сразу после текущего statement block, иначе будет ошибка сборки.
#
# Если, после вызова функции, это самое проигнорированное значение
# не является nil`ом, то управление сразу передается в нужную case-секцию.
#
# Еще можно потребовать выставлять значок "@" перед вызовом функции,
# с игнорированием значения:
#
# ''' function_call : '?' expression '(' expression_list ')' '''
#
# Если handle лок содержит только одну case ветку, то его можно записать
# в сокращенном виде: "() handle (error Error) (...)", без 'case'.
#
# Таким образом, даже конструкторы могут возвращать ошибки и можно спокойно
# делать вложенные вызовы функций, возвращающих ошибки. Круто.
#
# Handle-блок может быть сразу после любой (?) закрывающей круглой скобки:
# - вызов функции
# - конструирование объекта (вызов конструктора)
# - блок statement`ов
#
# А как же if`ы (у ниж же есть ветки else) ?
# Думаю, handle блок может быть в конце if\else:
# if ... ( ... ) else ( ... ) handle (...)
#
# file := File (
# name = "test.txt"
# mode = File.readOnly
# ) handle (error File.OpenError) (
# handleFileOpenError(error)
# )
#
#
# Объявление переменных
# ---------------------
#
# a := Integer() # без инициализации?
# a := Integer(0)
# a := 0 # вывод типа из литерала
# parser := Parser(Lexer)
#
# Из руководства к GoLang:
#
# Within a composite literal of array, slice, or map type T, elements
# that are themselves composite literals may elide the respective
# literal type if it is identical to the element type of T.
# Similarly, elements that are addresses of composite literals
# may elide the &T when the element type is *T.
#
#
# Scope Bound Resource Management
# -------------------------------
#
# Реализовать Scope Bound Resource Management (RAII).
#
#
# Semantic diff
# -------------
#
# А еще я хочу свой нормальный diff, генерирующий читаемые патчи.
#
#
# Introspection
# -------------
#
# Интроспекция, reflection, очень.
#
#
# Список параметров переменной длинны для шаблонов
# ------------------------------------------------
#
# Что бы реализация для 2х-мерного вектора не отличалась от 3х-мерного.
#
#
# datatype.py
# -----------
#
# Каждое выражение должно иметь поле "тип".
#
#
# separator ::= ',' OR '\n' - как такая идея?
#
# Давать временным переменным в генерируемом коде более понятные имена,
# если есть возможность. Наприме, основываться на имени функции.
#
# http://c2.com/cgi/wiki?GoodSyntax
#
# Конструктор - просто инициализатор.
#
# import unittest
# tests = unittest.defaultTestLoader.discover('.')
# unittest.runner.TextTestRunner().run(tests)
#
#
# Выделение памяти в куче
# -----------------------
#
# parser := alloc<Parser>(alloc<Lexer>())
#
#
# camelCase
# ---------
#
# English has words like TCP, DNA and WTF. Should a TCP socket class
# be called TCPSocket or TCPsocket? What about a TCPIPSocket?
# What if we need a tcpOpen method - should we call it TCPOpen,
# like a class, to preserve the natural case of an acronym,
# or should it be TCPopen, so that the lowercase "o" conveys the fact
# that it's a function?"
#
# В этом, гм, языке вот так: tcpOpen - func, TcpIpSocket - class
#
#
# Операторы (утверждения) для создания-изменения перемнных
# --------------------------------------------------------
#
# - '=' - присвоить значение выражения справа от оператора куску памяти,
# указанному в переменной слева
# - '<=' - изменить адрес памяти, куда ссылается переменная
# - ':=' - создать переменную (указатель на кусок памяти), затем то же,
# что и '<='
# - '::=' - создать переменную и выделить для нее кусок памяти на стеке,
# затем то же, что и '='
#
#
# Примеры
# -------
#
# - создание переменной:
#
# - misery:
#
# # создаем изменяемую ('*' на конце имени) переменную
# # целого типа со значением 1 ...
# a* := Int(1)
#
# # затем присваиваем ей значение 2 ...
# a* = 2
#
# - misery, аналогично, но через оператор '::=':
#
# a* ::= 1
# a* = 2
#
# - ansi_c:
#
# /* создаем переменную - просто указатель на Int ... */
# Int* a;
#
# /* выделяем память на стеке для объекта Int(1) ... */
# Int tmp_0;
#
# /* вызываем конструктор Int(1) - в данном случае
# просто присваиваем значение ...
# */
# tmp_0 = 1;
#
# /* связываем имя 'a' и участок памяти, созданный
# выражением 'Int(1)' ...
# */
# a = &tmp_0;
#
# /* а теперь уже просто меняем значение куска памяти, на который
# ссылается переменная ...
# */
# *a = 2
#
# - чуть более сложный пример:
#
# - misery:
# a ::= plus(1, 2)
#
# - ansi_c:
# /* Где-то в отдельной секции ... */
# Int const_0 = 1;
# Int const_0 = 2;
#
# /* встроенная функция, реализована в стандартной библиотеке */
# void plus_Int(Int** __result, Int* a, Int* b) {
# **_result = *a + *b;
# }
#
# /* Оператор '::=' создает переменную и сразу же выделяет
# для нее память на стеке ...
# */
# Int tmp0;
# Int* a = &tmp0;
# plus_Int(&a, &const_0, &const_1);
#
#
# Если имя переменной кончается на '*', то она изменяема.
# При передаче изменяемого параметра в функцию, перед ним
# надо поставить '+'.
#
#
# M, R prefixes
# -------------
#
# MyClass := class {
# # поле же должно быть инциализировано, так?
# next Opt<R:MyClass>
# data Int32
# }
#
# start := func {
# m := MyClass() # ERROR: 'next' is uninitialized
# m2 := MyClass (
# data = 32
# next = Opt.None
# )
# m3 := MyClass (
# data = 1
# next = MyClass (
# data = 2
# next = MyClass (
# data = 3
# next = Opt.None
# )
# )
# )
# }
#
# ===============================================================================
import (
std (
Integer
Float
Vector
String
printLine*
)
date.Date
)
func update (
gameObjects Vector<GameObject>
time Integer32
) (
do each(gameObjects) |gameObject| (
gameObject.update(time)
)
)
# plain c-struct
#
class Book (
store (
name String
authors Vector<String>
publisherName String
publishingDate Date
)
attach (
enum Status <Integer> (
clubs
diamonds
spades
hearts
error
)
)
)
class Library (
store (
_books Vector<Book>
)
attach (
typedef AddBookError Error
func addBook* (book Book) -> (error AddBookError) (
error = this._books.append(book)
)
typedef RemoveBookError Error
func removeBook* (book Book) -> (error RemoveBookError) (
error = this._books.remove*(book)
)
)
)
class Librarian (
store (
_library Library
)
attach (
func Librarian (library* Library) (
this._library = library*
)
typedef BorrowBookError Error
func borrowBook* (book Book) -> BorrowBookError (
# найти экземпляр в библиотеке
# пометить экзампляр как отданый
)
typedef FindBookByNameError Error
func findBookByName (bookName String) -> (Book, FindBookByNameError) (
# ...
)
)
)
class Client (
borrow (
basicPerson BasicPerson
)
store (
_firstName String
_familyName String
_fatherName String
_birthDate Date
)
attach (
func Client (
firstName String
familyName String
fatherName String
) (
# ???
)
typedef BorrowBookError Error
func borrowBook* (
librarian* Librarian
book Book
) -> error:BorrowBookError (
error = librarian*.borrowBook*(book)
)
typedef ReturnBookError Error
func returnBorrowedBook (
librarian Librarian
book Book
) -> ReturnBookError (
# ???
)
)
)
func _makeTestLibrary () -> (library Library) (
books := Vector<Book> (
Book (
name = "The C Programming Language"
edition = 1
publisherName = "Prentice Hall"
publishingDate = Date(year=1978)
authors = ("Dennis M. Ritchie" "Brian W. Kernighan")
)
Book (
name = "Structure and Interpretation of Computer Programs"
edition = 2
publisherName = "The MIT Press"
publishingDate = Date(year=1996)
authors = ("Harold Abelson" "Gerald Jay Sussman")
)
)
library.registerBooks*(books)
)
func main (arguments Array<String>) (
library* := _makeTestLibrary()
librarian* := Librarian(library*=getPointer(library*))
krBookName := "The C Programming Language"
krBook := ?librarian*.findBookByName(krBookName)
client* := Client (
firstName = "Andrey"
familyName = "Lesnikov"
fatherName = "Alexandrovich"
)
?client*.borrowBook*(librarian*=librarian*, book=krBook)
?client*.returnBorrowedBook(librarian=librarian*, book=krBook)
) handle (
case error:Librarian.FindBookByNameError (
console.printLine*("Can not find book '", krBookName, "'!")
)
case error:Client.BorrowBookError (
console.printLine*("Can not borrow book '", krBookName, "'!")
)
case error:Client.ReturnBorrowedBookError (
console.printLine*("Can not return book '", krBookName, "'!")
)
)
#!/bin/sh
clear
pep8 *.py --exclude=parsetab.py
# Example output: '71%'
run_one_module_unit_tests() {
echo -n $1': '
cov="coverage run --branch --source=$1"
cmd="python -m $cov -m unittest test_$1"
run_report=$($cmd 2>&1)
# $cmd
# 1 2 3 4 5 6(!)
# Name Stmts Miss Branch BrMiss Cover
# generator 254 73 70 20 71%
python -m coverage report | grep $1 | awk '{print($6)}'
# generate html docs in separate folder
python -m coverage html -d htmlcov_$1
}
# filelist='datatype generator misc parse'
# for file in $filelist; do run_one_module_unit_tests $file; done
python -B -m coverage run --branch -m unittest discover
python -B -m coverage html -d htmlcov
python -B -m coverage report
# rm *_out.c *_out.exe
# pylint --rcfile=.pylint.rc *.py -f html > x.html
#!env python
# -*- coding: utf-8 -*-
# See LICENSE file for copyright and license details
import subprocess
call = subprocess.call
check_output = subprocess.check_output
def run_one_module_unit_tests(file):
print file
call([
'python',
# '-m', 'coverage', 'run', '--branch',
# '--source=', 'misery/' + file + '.py',
'-m', 'unittest', 'misery/test_' + file + '.py',
])
# 1 2 3 4 5 6(!)
# Name Stmts Miss Branch BrMiss Cover
# generator 254 73 70 20 71%
# python -m coverage report | grep $1 | awk '{print($6)}'
# output = check_output(['python', '-m', 'coverage', 'report'])
# print file + ': ' + '\n' + output
# generate html docs in separate folder
# call(['python', '-m', 'coverage', 'html', '-d', 'htmlcov_' + file])
filelist = [
# 'datatype',
# 'generator',
# 'misc',
# 'parse',
]
for file in filelist:
run_one_module_unit_tests(file)
call(['pep8', 'misery', '--exclude=parsetab'])
call([
'python', '-m', 'coverage', 'run', '--branch',
'-m', 'unittest', 'discover',
])
call(['python', '-m', 'coverage', 'report'])
call(['python', '-m', 'coverage', 'html', '-d', 'htmlcov'])
# pylint --rcfile=.pylint.rc *.py -f html > x.html
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment