সীমাবদ্ধ ভাষা


28

এই সাইটে আমার প্রিয় ধরণের চ্যালেঞ্জগুলির মধ্যে একটি চ্যালেঞ্জ। এই চ্যালেঞ্জগুলি একটি কম্পিউটার ট্র্যাকটেবল সীমাবদ্ধতা আরোপ করে যা সম্ভাব্য উত্তরের উত্সগুলি পাস করতে হবে। আমি এই চ্যালেঞ্জগুলিকে পছন্দ করি তাই আমি কিছু সময়ের জন্য এই চ্যালেঞ্জগুলিতে জয়লাভ করার জন্য পরিকল্পিত একটি গল্ফ ভাষা নিয়ে কাজ করছি। এখন আমি আপনার কাছে চ্যালেঞ্জটি প্রসারিত করতে চাই। আপনার কাজটি হ'ল বিভিন্ন ভাষার সীমাবদ্ধ উত্স কার্যগুলি সমাধান করার জন্য একটি ভাষা নকশা করা। আপনি কোনও ভাষা ডিজাইন এবং প্রয়োগ করতে পারবেন, যার সময়ে পরিবর্তনগুলি এবং নতুন সংযোজনগুলি প্রতিযোগিতামূলক হবে এবং সমস্ত দাখিলগুলি গাঁটলে যাবে চ্যালেঞ্জগুলির ।

স্কোরিং

চ্যালেঞ্জ পোস্ট হওয়ার আগে আমি সমাধানের সহজ চ্যালেঞ্জগুলির একটি তালিকা এবং অনুসরণ করার উত্স বিধিনিষেধের একটি তালিকা নিয়ে আসব। প্রতিটি মিলের চ্যালেঞ্জ এবং উত্স বিধিনিষেধের জন্য আপনার ভাষা 0 থেকে 2 পয়েন্টের মধ্যে উপার্জন করতে পারে। (10 টি চ্যালেঞ্জ এবং 10 টি নিষেধাজ্ঞাগুলি 100 টি মোট সংমিশ্রণের দিকে পরিচালিত করবে) ভাষার স্কোর

  • 1 পয়েন্ট যদি এটি 150 বাইটের নিচে সীমাবদ্ধতার সাথে টাস্কটি সম্পন্ন করতে পারে
  • 2 পয়েন্ট যদি সমাধান হয় যে কোনও ভাষার প্রতিযোগিতার সংক্ষিপ্ততম সমাধান (উভয় ভাষা টাই হওয়ার ক্ষেত্রে 2 পয়েন্ট করবে)
  • 0 পয়েন্ট যদি তারা এমন কোনও প্রোগ্রাম তৈরি করতে অক্ষম হয় যা 150 বাইটের কমের মধ্যে বিধিনিষেধের অধীনে কাজটি সম্পন্ন করে।

আপনার স্কোর হ'ল প্রতিটি সম্ভাব্য ম্যাচে উপার্জিত সমস্ত পয়েন্টের যোগফল। গোলটি সর্বোচ্চ স্কোর পাওয়া। অন্যান্য লোকেরা আপনাকে প্রতিটি চ্যালেঞ্জের সমাধান সমাধান করতে এবং আপনার স্কোর উন্নত করতে সহায়তা করতে পারে।

আমি পোস্টিংয়ের সময় প্রতিটি তালিকার 4 টি আইটেম এবং দ্বিতীয় উত্তরের এক সপ্তাহ পরে অতিরিক্ত 8 টি প্রকাশ করব। উভয় অংশ প্রথম সপ্তাহের আগে প্রকাশিত হয়েছে এমন কোনও মিলের মধ্যে আপনাকে কেবলমাত্র 1 পয়েন্ট (সংক্ষিপ্ত জমা গণনা করা যাবে না) এর অনুমতি দেওয়া হবে। এইভাবে আপনি কাজ করার সময় আপনার ভাষা কতটা ভাল স্ট্যাক করে তার একটি ধারণা পেতে পারেন, তবে আপনি সমস্ত চ্যালেঞ্জ এবং বিধিনিষেধ তৈরি করতে কেবল আপনার ভাষা ডিজাইন করতে পারবেন না।

আমি প্রশ্নের সাথে উদ্দেশ্যযুক্ত বিভাগগুলির একটি হ্যাশ অন্তর্ভুক্ত করব যাতে আপনি নিশ্চিত হন যে কোনও পক্ষকে সুবিধা দেওয়ার জন্য আমি সপ্তাহে এগুলি পরিবর্তন করব না। এছাড়াও আমি সপ্তাহ শেষ না হওয়া পর্যন্ত কাউকে লুকানো প্যারামিটারগুলি বলব না বা নিজেই চ্যালেঞ্জের সাথে প্রতিযোগিতা করব না।

পূর্ব বিদ্যমান ভাষা

এই চ্যালেঞ্জটি সমস্ত পূর্ব-বিদ্যমান ভাষার জন্য উন্মুক্ত তবে আপনি যদি ভাষাটির লেখক না হন তবে দয়া করে আপনার উত্তরটিকে একটি সম্প্রদায় উইকি তৈরি করুন যাতে আমাদের সম্প্রদায়ের অন্যান্য সদস্যরা সরাসরি স্কোরটিতে অবদান রাখতে পারে। কমান্ড লাইন পতাকাগুলি কোনও বিধিনিষেধ মানতে হবে না তবে প্রতিটি প্রোগ্রাম একই কমান্ড লাইন আর্গুমেন্টের সাথে চালানো উচিত (উদাহরণস্বরূপ আপনার একটি বেছে নেওয়া এবং এটির সাথে থাকা উচিত)। এগুলি আপনার বাইট গণনায় যোগ করে না।

চ্যালেঞ্জ এবং বিধিনিষেধ

আপনার বাইনারিগুলির ASCII এনকোডিংয়ে কোড-পৃষ্ঠা আপনি ব্যবহার না করেই বিধিনিষেধগুলি প্রয়োগ করা হয়। এই সাইটগুলিতে বিদ্যমান প্রশ্নের সাথে এই লিঙ্কগুলি রয়েছে যার থেকে তারা চ্যালেঞ্জগুলির জন্য io প্রয়োজনীয়তা এবং বিধিনিষেধের জন্য উত্স বিধিনিষেধের উত্তরাধিকারী। আপনি যে কোনও লিঙ্কযুক্ত চ্যালেঞ্জ সম্পর্কে "বিল্টিনগুলি নিষিদ্ধ" বা বিদ্যমান মেটা sensকমত্যকে উপেক্ষা করে কিছু উপেক্ষা করতে পারেন can

সতর্কতার শব্দ হিসাবে: আইনজীবি শাসনের চেষ্টা করবেন না; আমি জানি এটি একটি প্রতিযোগিতা তবে কারণ এখানে মূলত 100 টি বিভিন্ন সাব-চ্যালেঞ্জ চ্যালেঞ্জ রয়েছে এবং আমি কেবল গ্যারান্টি দিতে পারি না যে এগুলি সমস্ত সম্পূর্ণরূপে অপ্রয়োজনীয় হবে। শুধু মজা করার চেষ্টা করুন।

চ্যালেঞ্জ

বিধিনিষেধ

বাকি মানদণ্ডগুলির একটি sha512 হ্যাশ রয়েছে:

4de5eca33c6270798606cf1412820c4ce112d8b927ef02877f36795b2b15ffacca51ea598fa89b8d6bc9f4cde53810e0e7ade30e536e52e28f40a6a13841dfc5  -


1
ডাউনওয়েট করতে এখানে এসেছিলেন, তারপরে অনুমানটি পড়ুন। +1
ট্রাইকোপলাক্স

3
আমি যদি এমন কোনও ভাষা বিকাশ করি যেখানে খালি প্রোগ্রামটি পূর্ণসংখ্যার একটি তালিকা সাজায়, হ্যালো ওয়ার্ল্ড প্রিন্ট করে, নির্ধারণ করে যে বন্ধনীগুলি ভারসাম্যযুক্ত কিনা বা ইনপুটটিতে কী আছে তার উপর নির্ভর করে প্রাথমিকতা পরীক্ষা করে? আমি এই চ্যালেঞ্জগুলিকে কেবল উদাহরণ হিসাবে রাখার পরামর্শ দিয়েছি এবং অন্যান্য, অনাবৃত চ্যালেঞ্জগুলিতে একচেটিয়াভাবে জমা দেওয়ার জন্য পরামর্শ দিচ্ছি
লিও


1
@ কমরেডস্পার্কলপনি সকল প্রোগ্রামের জন্য কমান্ড লাইনের পতাকাগুলি সমান হওয়া দরকার।
গম উইজার্ড

উত্তর:


5

প্রস্থ

দোভাষী দের কাজ এখনও চলছে (আমার বেশ কয়েকটি অব্যবহৃত কমান্ড স্লট রয়েছে)। আরও নথিপত্র সহ রেপো এখানে পাওয়া যাবে

ডেনিস এক মিনিটেরও কম সময় আগে টিআইওতে প্রস্থ যুক্ত করেছিলেন: এটি অনলাইনে চেষ্টা করুন!

প্রস্থটি আমি এই প্রশ্নের প্রথমে ফেলেছি এমন ধারণাগুলির উপর ভিত্তি করে আমি সম্প্রতি তৈরি একটি মজাদার স্ট্যাক-ভিত্তিক ভাষা । এটি সম্পূর্ণরূপে কতটা প্রশস্ত, কম বেশি, কোনও চিঠি "নিয়মিত" ফন্টে থাকে তার উপর ভিত্তি করে। কেবলমাত্র অক্ষরগুলি হ'ল অক্ষর, বড় হাতের অক্ষর এবং ছোট হাতের অক্ষর। অন্য সমস্ত চরিত্র উপেক্ষা করা হয়। আমি চিঠিগুলিকে 10 টি বিভিন্ন প্রস্থের বিভাগে বিভক্ত করেছি, যা প্রস্থে 10 টি বিভিন্ন ক্রিয়া গঠন করে:

0: i j l                     # do while counter > 0
1: f r t I                   # end
2: c k s v x y z J           # 0 in commands
3: a b d e g h n o p q u L   # separator (no-op)
4: F T Z                     # push base 10 number, using left side row titles (width numbers); terminated with original char
5: A B E K P S V X Y         # 1 in commands
6: w C D H N R U             # 2 in commands
7: G O Q                     # push string literal; sets of 2 width numbers equate to index in printable ASCII; terminated with original char
8: m M                       # if top of stack
9: W                         # else

2, 5এবং 6কমান্ডগুলিতে অ্যাক্সেস সরবরাহ করে, যার বেশিরভাগ স্ট্যাকের সাথে ইন্টারঅ্যাক্ট করে। info.txtগিথুব রেপোতে আরও তথ্য পৃষ্ঠাতে পাওয়া যাবে ।

এটি দোভাষী এর পাইথন কোড। আমার কাছে এখনও যুক্ত করার জন্য বেশ কয়েকটি কমান্ড রয়েছে এবং আমি কীভাবে ত্রুটি-পরিচালনা নিয়ে কাজ করব তা বিবেচনা করছি তবে অন্যথায় এটি সম্পূর্ণ হওয়া উচিত। (আরও বিমূর্ত সিনট্যাক্সের সাথে পরীক্ষার অনুমতি দেওয়ার জন্য আমি কোনও সময় একটি পতাকাও যুক্ত করতে যাচ্ছি, কারণ অন্যথায় এই ভাষাটি কাজ করার জন্য একটি বিশাল ব্যথা)

import sys
import math
import numbers
import functools

try:
    file = sys.argv[1]
except IndexError:
    file = "source.wide"

with open(file) as f:
    source = f.read()

translation = ("ijl", "frtI", "cksvxyzJ", "abdeghnopquL", "FTZ", "ABEKPSVXY", "wCDHNRU", "GOQ", "mM", "W")
chars = "".join(sorted("".join(translation)))
strings = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\n\t"""


def trans(letter):
    for each in translation:
        if letter in each:
            return translation.index(each)

COMMAND = "COMMAND"
COMMANDS = (2, 5, 6)
NUMBER = "NUMBER"
STRING = "STRING"
DO = "DO"
IF = "IF"
ELSE = "ELSE"
END = "END"
SEPARATOR = "SEPARATOR"


class Token:
    def __init__(self, val, type_):
        self.val = val
        self.type = type_


class Lexer:
    def __init__(self, src):
        self.src = src
        self.pos = 0
        self.char = self.src[self.pos]

    def read(self):
        if self.char is None:
            return None

        command = trans(self.char)

        if command == 0:
            self.advance()
            return Token(0, DO)
        elif command == 1:
            self.advance()
            return Token(1, END)
        elif command == 3:
            self.advance()
            return Token(3, SEPARATOR)
        elif command == 4:
            return self.read_num()
        elif command == 7:
            return self.read_str()
        elif command == 8:
            self.advance()
            return Token(8, IF)
        elif command == 9:
            self.advance()
            return Token(9, ELSE)
        else:
            return self.read_cmd()

    def advance(self):
        self.pos += 1

        try:
            self.char = self.src[self.pos]
        except IndexError:
            self.char = None

    def read_num(self):
        delim = self.char
        self.advance()

        res = ""
        while self.char is not None and self.char != delim:
            res += str(trans(self.char))
            self.advance()

        self.advance()

        return Token(int(res), NUMBER)

    def read_str(self):
        def read_char():
            res_ = str(trans(self.char))
            self.advance()
            res_ += str(trans(self.char))
            self.advance()
            try:
                return strings[int(res_)]
            except IndexError:
                return " "

        delim = self.char
        self.advance()

        res = ""
        while self.char is not None and self.char != delim:
            res += read_char()

        self.advance()

        return Token(res, STRING)

    def read_cmd(self):
        command = ""
        while self.char is not None and trans(self.char) in COMMANDS and len(command) <= 4:
            command += str(COMMANDS.index(trans(self.char)))
            self.advance()

        return Token(command, COMMAND)

source = "".join(filter(lambda c: c in chars, source))

stack = []
backburner = []
counter = 0


def set_counter(val):
    global counter
    counter = int(val)

    if counter < 0:
        counter = 0


def set_stack(val):
    global stack
    stack = val


def num_input():
    inp = input()
    try:
        stack.append(int(inp))
    except ValueError:
        try:
            stack.append(float(inp))
        except ValueError:
            pass


def flip_ends():
    if len(stack) > 1:
        stack[0], stack[-1] = stack[-1], stack[0]


def clear_stack():
    global stack
    stack = []


def reload_stack(is_top):
    global backburner, stack

    if is_top:
        stack.extend(backburner)
    else:
        stack = backburner + stack

    backburner = []


# /programming//a/15285588/7605753
def is_prime(n):
    if n == 2 or n == 3:
        return True
    if n < 2 or n % 2 == 0:
        return False
    if n < 9:
        return True
    if n % 3 == 0:
        return False
    r = int(math.sqrt(n))
    _f = 5
    while _f <= r:
        if n % _f == 0:
            return False
        if n % (_f + 2) == 0:
            return False
        _f += 6
    return True


def error():
    raise Exception

commands = {
    "0": lambda: stack.append(stack[-1]),
    "1": lambda: stack.append(stack.pop(-2)),
    "2": lambda: stack.pop(),
    "00": lambda: set_counter(stack[-1]),
    "01": lambda: stack.append(len(stack)),
    "02": lambda: stack.append(input()),
    "10": num_input,
    "11": lambda: stack.append(str(stack.pop())),
    "12": lambda: stack.append(int(stack.pop())),
    "20": lambda: set_counter(counter + 1),
    "21": lambda: set_counter(counter - 1),
    "22": lambda: print(stack.pop()),
    "000": lambda: stack.append(float(stack.pop())),
    "001": lambda: stack.append(-stack.pop()),
    "002": lambda: stack.append(not stack.pop()),
    "010": lambda: stack.append(stack.pop(-2) + stack.pop()),
    "011": lambda: stack.append(stack.pop(-2) - stack.pop()),
    "012": lambda: stack.append(stack.pop(-2) / stack.pop()),
    "020": lambda: stack.append(stack.pop(-2) // stack.pop()),
    "021": lambda: stack.append(stack.pop(-2) * stack.pop()),
    "022": lambda: stack.append(stack.pop(-2) % stack.pop()),
    "100": lambda: stack.append(math.factorial(stack.pop())),
    "101": lambda: stack.append(str(stack.pop(-2)) + str(stack.pop())),
    "102": lambda: stack.append(math.pow(stack.pop(-2), stack.pop())),
    "110": lambda: stack.append(math.sqrt(stack.pop())),
    "111": lambda: stack.append(math.log(stack.pop(-2), stack.pop())),
    "112": lambda: stack.append(~stack.pop()),
    "120": lambda: stack.append(stack.pop(-2) | stack.pop()),
    "121": lambda: stack.append(stack.pop(-2) & stack.pop()),
    "122": lambda: stack.append(stack.pop(-2) << stack.pop()),
    "200": lambda: stack.append(stack.pop(-2) >> stack.pop()),
    "201": lambda: stack.append(stack.pop(-2)[stack.pop()]),
    "202": lambda: stack.append(str(stack.pop(-2)) * stack.pop()),
    "210": lambda: stack.append(counter),
    "211": lambda: set_counter(stack.pop()),
    "212": lambda: stack.extend(list(str(stack.pop()))),
    "220": flip_ends,
    "221": lambda: stack.append(len(stack[-1])),
    "222": lambda: print(stack[-1]),
    "0000": lambda: stack.reverse(),
    "0001": lambda: stack.sort(),
    "0002": lambda: stack.append(stack[counter]),
    "0010": lambda: stack.append(stack[stack.pop()]),
    "0011": 0,
    "0012": 0,
    "0020": lambda: stack.append(sum(n for n in stack if isinstance(n, numbers.Number))),
    "0021": lambda: stack.append(functools.reduce(lambda x, y: x*y, [n for n in stack if isinstance(n, numbers.Number)], 1)),
    "0022": 0,
    "0100": lambda: (backburner.extend(stack), clear_stack()),
    "0101": lambda: reload_stack(True),
    "0102": lambda: reload_stack(False),
    "0110": lambda: backburner.append(stack.pop()),
    "0111": lambda: backburner.append(list(stack.pop())),
    "0112": lambda: stack.pop().split(stack.pop()),
    "0120": lambda: stack.append(backburner[-1]),
    "0121": lambda: (lambda depth=stack.pop(): (set_stack(stack[-depth:]), backburner.append(stack[:depth])))(),
    "0122": lambda: (lambda depth=stack.pop(): (set_stack(stack[:-depth]), backburner.append(stack[depth:])))(),
    "0200": lambda: set_stack([stack.pop().join(stack)]),
    "0201": lambda: set_stack(["".join(stack)]),
    "0202": lambda: (lambda depth=stack.pop(-2): set_stack(stack[-depth:] + [stack.pop().join(stack[:depth])]))(),
    "0210": lambda: (lambda depth=stack.pop(): set_stack(stack[-depth:] + ["".join(stack[:depth])]))(),
    "0211": 0,
    "0212": 0,
    "0220": lambda: stack.append(stack.pop().split(stack.pop())),
    "0221": lambda: stack.append(stack.pop().split(", ")),
    "0222": 0,
    "1000": lambda: stack.append(is_prime(stack[-1])),
    "1001": lambda: stack.append((lambda s=stack.pop(): s == s[::-1])()),
    "1002": lambda: stack.append(1 / stack.pop()),
    "1010": lambda: stack.append(stack.pop() - 1),
    "1011": lambda: stack.append(stack.pop() + 1),
    "1012": lambda: stack.append(stack.pop() * 2),
    "1020": lambda: stack.append(stack.pop() / 2),
    "1021": lambda: stack.append(stack.pop() ** 2),
    "1022": lambda: float("." + str(stack.pop())),
    "1100": lambda: stack.append(stack.pop() == stack.pop()),
    "1101": lambda: stack.append(stack.pop() != stack.pop()),
    "1102": lambda: stack.append(stack.pop() > stack.pop()),
    "1110": lambda: stack.append(stack.pop() < stack.pop()),
    "1111": lambda: stack.append(stack.pop() >= stack.pop()),
    "1112": lambda: stack.append(stack.pop() <= stack.pop()),
    "1120": lambda: stack.append(stack.pop() in stack),
    "1121": lambda: stack.append(stack.pop() in backburner),
    "1122": lambda: stack.append(stack.pop() == counter),
    "1200": lambda: stack.append(stack.pop() in stack.pop()),
    "1201": lambda: stack.append(stack.pop(-2).find(stack.pop())),
    "1202": 0,
    "1210": 0,
    "1211": 0,
    "1212": lambda: stack.append(stack.pop().lower()),
    "1220": lambda: stack.append(stack.pop().upper()),
    "1221": lambda: stack.append(ord(stack.pop())),
    "1222": lambda: stack.append(chr(stack.pop())),
    "2000": lambda: stack.append(math.floor(stack.pop())),
    "2001": lambda: stack.append(math.ceil(stack.pop())),
    "2002": lambda: stack.append(round(stack.pop())),
    "2010": lambda: stack.append(abs(stack.pop())),
    "2011": 0,
    "2012": 0,
    "2020": lambda: stack.append(len(stack.pop())),
    "2021": 0,
    "2022": 0,
    "2100": lambda: stack.append(min(stack)),
    "2101": lambda: stack.append(max(stack)),
    "2102": lambda: stack.append(stack.count(stack.pop())),
    "2110": lambda: stack.append(sum(stack) / len(stack)),
    "2111": 0,
    "2112": 0,
    "2120": 0,
    "2121": 0,
    "2122": 0,
    "2200": lambda: stack.append(stack.pop(-3).replace(stack.pop(-2), stack.pop())),
    "2201": lambda: stack.append(stack.pop(-3).replace(stack.pop(-2), stack.pop(), 1)),
    "2202": lambda: stack.append(stack.pop(-2).replace(stack.pop(), "")),
    "2210": lambda: stack.append(stack.pop(-2).replace(stack.pop(), "", 1)),
    "2211": 0,
    "2212": lambda: stack.append(eval(stack.pop())),
    "2220": lambda: stack.append(eval(input())),
    "2221": lambda: print(stack[-1]),
    "2222": lambda: error()
}


def run_cmd(name):
    global stack, counter, backburner

    state = {
        "stack": list(stack),
        "counter": counter,
        "backburner": list(backburner)
    }

    # TODO: unknown command

    try:
        commands[name]()
    except IndexError:
        stack = state["stack"]
        counter = state["counter"]
        backburner = state["backburner"]


class AST:
    pass


class Main(AST):
    def __init__(self):
        self.nodes = []


class Do(AST):
    def __init__(self, node):
        self.node = node


class If(AST):
    def __init__(self, first, second):
        self.first = first
        self.second = second


class Literal(AST):
    def __init__(self, val):
        self.val = val


class Command(AST):
    def __init__(self, val):
        self.val = val


class Parser:
    def __init__(self, lexer):
        self.lexer = lexer
        self.token = None

    def parse(self):
        pgm = Main()
        self.token = self.lexer.read()

        while self.token is not None and self.token.type != END and self.token.type != ELSE:
            if self.token.type == DO:
                pgm.nodes.append(Do(self.parse()))

            elif self.token.type == NUMBER or self.token.type == STRING:
                pgm.nodes.append(Literal(self.token.val))

            elif self.token.type == IF:
                first = self.parse()

                if self.token.type == ELSE:
                    second = self.parse()
                else:
                    second = None

                pgm.nodes.append(If(first, second))

            elif self.token.type == COMMAND:
                pgm.nodes.append(Command(self.token.val))

            self.token = self.lexer.read()

        return pgm


class Interpreter:
    def __init__(self, tree):
        self.tree = tree

    def visit(self, node):
        method_name = "visit_" + type(node).__name__
        visitor = getattr(self, method_name.lower())
        return visitor(node)

    def interpret(self):
        self.visit(self.tree)

    def visit_main(self, node):
        for each in node.nodes:
            self.visit(each)

    def visit_do(self, node):
        while counter:
            self.visit(node)

    def visit_if(self, node):
        if stack[-1]:
            self.visit(node.first)
        elif node.second:
            self.visit(node.second)

    def visit_literal(self, node):
        stack.append(node.val)

    def visit_command(self, node):
        run_cmd(node.val)


if source == "":
    with open("info.txt") as f:
        info = f.read()

    print(info)
else:
    main = Parser(Lexer(source)).parse()

    interpreter = Interpreter(main)
    interpreter.interpret()

    try:
        sys.exit(stack[-1])
    except IndexError:
        pass
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.